export type PageLifecycleEventType = "show" | "hide" | "unload";
export type PageLifecycleState = "initializing" | "visible" | "hidden" | "unloaded";
export type PageLifecycleSource = "host" | "document";

export type PageLifecycleEvent = {
  type: PageLifecycleEventType;
  previous: PageLifecycleState;
  current: PageLifecycleState;
  /**
   * `host` means the event came from iframe navigation policy. `document`
   * means the browser is discarding the document and acts as an unload
   * fallback when a Host `dispose` message cannot be delivered first.
   */
  source: PageLifecycleSource;
};

export type PageLifecycleListener = (event: PageLifecycleEvent) => void;

/**
 * Framework-independent lifecycle exposed to every Page.
 *
 * Event subscriptions are best for imperative work at a transition boundary:
 * refresh data on show, pause a stream on hide, and close external resources
 * on unload. Each method returns an idempotent unsubscribe function that a UI
 * framework should call during component cleanup.
 */
export type PageLifecycle = {
  state: () => PageLifecycleState;
  /** Subscribes only to future transitions of the selected type. */
  on: (type: PageLifecycleEventType, listener: PageLifecycleListener) => () => void;
  /**
   * Subscribes to show and immediately replays it when already visible.
   *
   * Replay prevents a lazy-loaded Page from missing the initial Host event.
   */
  onShow: (listener: PageLifecycleListener) => () => void;
  /** Subscribes to hide and immediately replays it when already hidden. */
  onHide: (listener: PageLifecycleListener) => () => void;
  onUnload: (listener: PageLifecycleListener) => () => void;
};

export type PageLifecycleDispatcher = {
  lifecycle: PageLifecycle;
  /**
   * Applies one lifecycle transition. Repeated and post-unload transitions are
   * ignored, so Host `dispose` followed by browser `pagehide` emits one unload.
   */
  dispatch: (
    type: PageLifecycleEventType,
    source: PageLifecycleSource,
  ) => PageLifecycleEvent | null;
};

/**
 * Creates the lifecycle state machine for one iframe document.
 *
 * This module deliberately has no Solid or DOM dependency. A React, Vue or
 * other Page adapter can expose the same object without changing SDK behavior.
 */
export function createPageLifecycleDispatcher(): PageLifecycleDispatcher {
  let state: PageLifecycleState = "initializing";
  let lastEvent: PageLifecycleEvent | null = null;
  const listeners: Record<PageLifecycleEventType, Set<PageLifecycleListener>> = {
    show: new Set(),
    hide: new Set(),
    unload: new Set(),
  };

  function on(type: PageLifecycleEventType, listener: PageLifecycleListener): () => void {
    if (state === "unloaded") return () => {};
    listeners[type].add(listener);
    return () => listeners[type].delete(listener);
  }

  function onCurrent(type: "show" | "hide", listener: PageLifecycleListener): () => void {
    const unsubscribe = on(type, listener);
    if (lastEvent?.type === type) listener(lastEvent);
    return unsubscribe;
  }

  const lifecycle: PageLifecycle = {
    state: () => state,
    on,
    onShow: (listener) => onCurrent("show", listener),
    onHide: (listener) => onCurrent("hide", listener),
    onUnload: (listener) => on("unload", listener),
  };

  function dispatch(
    type: PageLifecycleEventType,
    source: PageLifecycleSource,
  ): PageLifecycleEvent | null {
    if (state === "unloaded") return null;
    const current = stateFor(type);
    if (current === state) return null;

    const event: PageLifecycleEvent = {
      type,
      previous: state,
      current,
      source,
    };
    state = current;
    lastEvent = event;
    for (const listener of [...listeners[type]]) listener(event);
    if (current === "unloaded") {
      for (const subscriptions of Object.values(listeners)) subscriptions.clear();
    }
    return event;
  }

  return { lifecycle, dispatch };
}

function stateFor(type: PageLifecycleEventType): PageLifecycleState {
  switch (type) {
    case "show":
      return "visible";
    case "hide":
      return "hidden";
    case "unload":
      return "unloaded";
  }
}
