// The app's own lifecycle + how it was opened (docs/app-lifecycle-plan.md). Events are semantic —
// "pause"/"resume", never Activity/Scene vocabulary — and the current values are synchronous pulls
// so render-loop code can read them without subscribing.

import { appEventsOn, appEventsOff, keyboardEventsOn, keyboardEventsOff } from "./appEvents"

export type AppState = "active" | "background"

type AppEventMap = {
  /** The app left the foreground (home button, tab hidden, another app on top). Delivered before
   *  the host halts the frame loop — the last chance to persist state / pause work. */
  pause: () => void
  /** The app returned to the foreground; frames are running again. */
  resume: () => void
  /** A link arrived while the app was running (warm deep link). `app.launchUrl` is already
   *  updated to the same value when this fires. */
  url: (url: string) => void
  /** The on-screen keyboard changed. `height` mirrors `app.keyboardHeight` (the raw overlap, see
   *  there); `duration` is the platform's keyboard animation duration in ms (0 where none) — pass
   *  it to `animateTo` to move a composer in sync with the keyboard. */
  keyboard: (height: number, duration: number) => void
}

export const app = {
  /** Current lifecycle state. `"background"` while the app is not the foreground app / the tab is
   *  hidden. `"active"` on hosts that don't track it. */
  get state(): AppState {
    return _creatorUtils.appState?.() === "background" ? "background" : "active"
  },
  /** The URL the app was (most recently) opened with — the deep link — or `null` for a plain
   *  launch. Warm links update it and fire the `"url"` event. */
  get launchUrl(): string | null {
    return _creatorUtils.getLaunchUrl?.() ?? null
  },
  /** On-screen keyboard height in logical px currently overlapping the app viewport, `0` when
   *  hidden. This is the RAW overlap, independent of the focused input's `keyboardShrink` policy —
   *  with the default shrink the layout already avoids the keyboard; the main consumer is overlay
   *  mode (`keyboardShrink: false`), e.g. a chat composer's `pb`. `0` on hosts without an
   *  on-screen keyboard. */
  get keyboardHeight(): number {
    return _creatorUtils.keyboardHeight?.() ?? 0
  },
  /** Re-run this app's bundle in a fresh world — the platform's `location.reload()`. Pass `url`
   *  to reboot with different launch arguments (the new run reads them via `app.launchUrl`),
   *  `null` to reboot with a cleared one, or omit it to replay the current one. No-op on hosts
   *  without world control (headless). */
  restart(url?: string | null): void {
    _creatorUtils.restart?.(url)
  },
  /** Leave this app. Inside the LeCodes viewer that returns to the launcher (the LeCodes home
   *  screen); a standalone-built app leaves to the platform instead — Android backgrounds it,
   *  iOS ignores the call (the platform forbids programmatic exit). */
  quit(): void {
    _creatorUtils.quit?.()
  },
  /** Global back-press fallback (Android back button/gesture — iOS has no system back). Fires
   *  only when nothing else consumed the press: no open widget/modal claimed it, the current
   *  screen has no `onBackPressed`, every pager is at its tab root and the router is at its
   *  stack root. The place for app-wide policy — e.g. double-press-to-quit:
   *
   *  ```ts
   *  let armedAt = 0
   *  app.onBackPressed(() => {
   *    if (Date.now() - armedAt < 2000) { app.quit(); return }
   *    armedAt = Date.now()
   *    toast("Press back again to exit")
   *  })
   *  ```
   *
   *  Replaces the previous handler; pass `null` to remove. */
  onBackPressed(callback: (() => void) | null): void {
    _creatorUI._backButtonCallback = callback ?? undefined
  },
  addEventListener<E extends keyof AppEventMap>(event: E, callback: AppEventMap[E]): void {
    if (event === "keyboard") {
      keyboardEventsOn(callback as (height: number, duration: number) => void)
      return
    }
    appEventsOn(event, callback as (data?: string) => void)
  },
  removeEventListener<E extends keyof AppEventMap>(event: E, callback: AppEventMap[E]): void {
    if (event === "keyboard") {
      keyboardEventsOff(callback as (height: number, duration: number) => void)
      return
    }
    appEventsOff(event, callback as (data?: string) => void)
  },
}
