/**
 * @file types.ts
 * @description Shared type system for iframe ↔ parent postMessage communication.
 *
 * Direction is documented per message type as: child → parent | parent → child | both
 */

/*================================= SMARTLINK MESSAGE MAP ==================================*/

export type SmartlinkMessageMap = {
  /** parent → child. Signals the smartlink to start. Sent in response to SMARTLINK_READY. */
  SMARTLINK_INIT: undefined;

  /** child → parent. Signals the child is mounted and ready to receive messages. */
  SMARTLINK_READY: undefined;

  /** child → parent. Signals parent to start the prepayment method flow. */
  SMARTLINK_PREPAYMENT_METHOD: { quote_id: string };

  /** parent → child. Signals the parent has completed the prepayment method flow. */
  SMARTLINK_PREPAYMENT_METHOD_COMPLETE: {
    success: boolean;
    payment_id: string;
    error?: string;
  };

  /** child → parent. Signals the current step completed successfully. */
  SMARTLINK_STEP_COMPLETE: {
    success?: boolean;
    source?: "payments" | "uploader" | "consent" | string;
    payment_data?: string;
    consent_data?: string;
    uploader_data?: string;
  };

  /** child → parent. Signals the user cancelled or closed the flow. */
  SMARTLINK_CANCELLED: undefined;

  /** child → parent. Reports an error to the parent. */
  SMARTLINK_ERROR: { details?: unknown; message?: string };

  /** child → parent. Requests the parent to resize the iframe element. */
  SMARTLINK_RESIZE: { height: number; width?: number };
};

/*======================================= BASE TYPES =======================================*/

export type AnyMessageMap = SmartlinkMessageMap;

export type SmartlinkMessage<
  K extends keyof SmartlinkMessageMap = keyof SmartlinkMessageMap,
> = {
  type: K;
  payload?: SmartlinkMessageMap[K];
  requestId?: string;
};

export type AnyIframeMessage = SmartlinkMessage;

/*================================ REQUEST → RESPONSE MAP ==================================*/

/**
 * Maps child → parent request types to the expected parent → child response type.
 * Only message types with a known response are included — these are the only types
 * accepted by the `request` function on SmartlinkChildAPI.
 */
export type SmartlinkRequestResponseMap = {
  SMARTLINK_READY: SmartlinkMessage<"SMARTLINK_INIT">;
  SMARTLINK_PREPAYMENT_METHOD: SmartlinkMessage<"SMARTLINK_PREPAYMENT_METHOD_COMPLETE">;
};

/*==================================== REQUEST ID HELPER ===================================*/

/**
 * Generates a unique requestId for correlating request/response message pairs.
 * Used internally by useSmartlinkMessaging — not typically called directly.
 */
export const createRequestId = (): string =>
  `req-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;

/*===================================== FACTORY HELPER =====================================*/

export function createSmartlinkMessage<K extends keyof SmartlinkMessageMap>(
  type: K,
  payload?: SmartlinkMessageMap[K],
  requestId?: string,
): SmartlinkMessage<K> {
  return {
    type,
    ...(payload !== undefined ? { payload } : {}),
    ...(requestId !== undefined ? { requestId } : {}),
  };
}

/*================================== MESSAGE REQUEST TYPES =================================*/

/**
 * Internal entry stored per in-flight request.
 * Keyed by requestId in the pending map inside useSmartlinkMessaging.
 */
export interface PendingRequest<TResponse> {
  resolve: (value: TResponse) => void;
  reject: (reason: Error) => void;
  timer: ReturnType<typeof setTimeout>;
}

/** Options passed to the `request` function. */
export interface MessageRequestOptions {
  /**
   * How long to wait for a response before rejecting.
   * Defaults to 5000ms.
   */
  timeout?: number;
}

/*======================================= SUBSCRIBER =======================================*/

/**
 * isValid — true if the message passed origin check + selfcare revalidation.
 * False means the parent would have silently dropped it internally; it is exposed
 * here so the consumer can decide whether to act or log.
 */
export type SmartlinkSubscriber = (
  message: SmartlinkMessage,
  event: MessageEvent<SmartlinkMessage>,
  isValid: boolean,
) => void;

/*================================= PARENT HOOK OPTIONS ====================================*/

interface BaseIframeMessagingOptions {
  /** The full URL of the iframe src. Used to derive and validate the expected origin. */
  pluginUrl: string;

  /** Ref to the iframe DOM element. Used for postMessage and resize. */
  iframeRef: React.RefObject<HTMLIFrameElement>;

  /**
   * Called when the child requests a resize.
   * Receives the dimensions the child requested. Return overridden dimensions to clamp,
   * or void to apply the requested values as-is.
   */
  onResize?: (requested: {
    height: number;
    width?: number;
  }) => { height: number; width?: number } | void;
}

export interface UseSelfcareParentMessagingOptions extends BaseIframeMessagingOptions {
  /**
   * Plugin ID used to look up and validate the plugin in the selfcare route registry.
   * Required for revalidation — without it all messages will fail the security check.
   */
  id: string;
}

export interface UseSmartlinkParentMessagingOptions extends BaseIframeMessagingOptions {}

/*================================ PARENT HOOK API — SMARTLINK =============================*/

export interface SmartlinkParentAPI {
  /** Send any smartlink-namespaced message to the child. */
  sendMessage: (message: SmartlinkMessage) => void;

  /** Send SMARTLINK_INIT — signals the smartlink to start after SMARTLINK_READY. */
  sendInit: (requestId?: string) => void;

  /**
   * Subscribe to all incoming smartlink messages from the child.
   * isValid indicates whether the message passed the origin check.
   * Returns unsubscribe fn.
   */
  subscribe: (handler: SmartlinkSubscriber) => () => void;

  /**
   * Subscribe to a specific smartlink message type.
   * isValid is passed as the third argument to the handler.
   * Returns unsubscribe fn.
   */
  subscribeTo: <K extends keyof SmartlinkMessageMap>(
    type: K,
    handler: (
      message: SmartlinkMessage<K>,
      event: MessageEvent<SmartlinkMessage>,
      isValid: boolean,
    ) => void,
  ) => () => void;
}
/*================================ CHILD HOOK OPTIONS — SMARTLINK ==========================*/

export interface UseSmartlinkMessagingOptions {
  /** Called on every incoming message before subscribers are notified. */
  onMessage?: (message: SmartlinkMessage) => void;

  /**
   * Origin to target when sending postMessage to the parent.
   * Defaults to '*'. Set to the known parent origin in production.
   */
  targetOrigin?: string;

  /**
   * Origins from which to accept incoming messages.
   * Defaults to '*'. Pass an array to restrict.
   */
  acceptOrigins?: "*" | string[];
}

/*=============================== CHILD HOOK API — SMARTLINK ===============================*/

export interface SmartlinkChildAPI {
  /** Send a raw smartlink message to the parent. */
  sendMessage: (message: SmartlinkMessage) => void;

  /** Send SMARTLINK_READY — call once on mount to trigger the init handshake. */
  notifyReady: () => void;

  /** Send SMARTLINK_STEP_COMPLETE — signal the current step finished. */
  completeStep: (
    result?: SmartlinkMessageMap["SMARTLINK_STEP_COMPLETE"] | undefined,
  ) => void;

  /** Send SMARTLINK_CANCELLED — signal the user cancelled the flow. */
  cancel: () => void;

  /** Send SMARTLINK_RESIZE — ask the parent to resize the iframe. */
  resize: (height: number, width?: number) => void;

  /**
   * Send a typed request and await the correlated response.
   * Only message types present in SmartlinkRequestResponseMap are accepted.
   * Rejects if no response arrives within the timeout (default 5000ms).
   */
  request: <K extends keyof SmartlinkRequestResponseMap>(
    type: K,
    payload?: SmartlinkMessageMap[K],
    options?: MessageRequestOptions,
  ) => Promise<SmartlinkRequestResponseMap[K]>;

  /**
   * Send SMARTLINK_PREPAYMENT_METHOD and await SMARTLINK_PREPAYMENT_METHOD_COMPLETE.
   * Resolves with the full response message including success and payment_id.
   */
  requestPrePaymentMethod: (
    quoteId: string,
    options?: MessageRequestOptions,
  ) => Promise<SmartlinkMessage<"SMARTLINK_PREPAYMENT_METHOD_COMPLETE">>;

  /** Subscribe to all incoming messages from the parent. Returns unsubscribe fn. */
  subscribe: (handler: SmartlinkSubscriber) => () => void;

  /** Subscribe to a specific message type from the parent. Returns unsubscribe fn. */
  subscribeTo: <K extends keyof SmartlinkMessageMap>(
    type: K,
    handler: (
      message: SmartlinkMessage<K>,
      event: MessageEvent<SmartlinkMessage>,
    ) => void,
  ) => () => void;

  /** True once notifyReady() has been called. Reactive — triggers re-render. */
  isReady: boolean;
}
