// src/test-hooks/types.ts — Plan 01-11 Task 2.
//
// SINGLE SOURCE OF TRUTH for the `globalThis.__mokoshTest` wire shape
// the Puppeteer UAT harness reads via `sw.evaluate` + `offPage.evaluate`.
//
// This type is imported by:
//   - src/test-hooks/sw-hooks.ts          (registers in SW isolate)
//   - src/test-hooks/offscreen-hooks.ts   (registers in offscreen isolate)
//   - tests/uat/lib/test-hook-contract.d.ts (manual mirror — keeps the
//     harness's import surface clean of any `import` reaching into `src/`;
//     drift risk documented in that file's preamble)
//
// Production-bundle invariant (T-1-11-01): this file's content is
// reachable from src/background/index.ts + src/offscreen/recorder.ts
// ONLY via a `if (import.meta.env.MODE === 'test') { await import(...) }`
// gated dynamic import. Vite statically replaces `import.meta.env.MODE`
// at build time; in production mode the literal-comparison branch is dead
// code and Rollup tree-shakes the entire `await import` away — so this
// file's content never lands in `dist/`. The Tier-1 grep gate
// `tests/background/no-test-hooks-in-prod-bundle.test.ts` enforces the
// absence of `__mokoshTest` (and the other hook surface strings) in
// every file under `dist/` after `npm run build`. See Plan 01-11
// RESEARCH §6 (Vite tree-shaking dynamic-import-behind-literal-guard).
//
// Cross-isolate note: SW and offscreen are SEPARATE JavaScript isolates
// with SEPARATE `globalThis` objects. The harness queries each surface
// via the appropriate `sw.evaluate(...)` or `offPage.evaluate(...)`.
// `__mokoshTest.getCurrentStream` is undefined in the SW isolate;
// `__mokoshTest.handlers` is populated only in the SW isolate. The
// offscreen-side init reuses the same type to keep the harness's
// reasoning unified — runtime null/undefined are the "not in this
// context" signals.
//
// References:
//   - Vite `import.meta.env.MODE`:
//     https://vite.dev/guide/env-and-mode.html
//   - W3C Screen Capture MediaStream: https://www.w3.org/TR/screen-capture/

/**
 * Wire shape of `globalThis.__mokoshTest` exposed by the Plan 01-11 test
 * hooks. Each field is described inline; null/undefined values carry
 * load-order or cross-isolate semantics documented per field.
 */
export interface MokoshTestSurface {
  /**
   * Captured chrome.* event-listener handler references. The SW-side
   * hook (sw-hooks.ts) monkey-patches `addListener` so the FIRST call
   * for each event captures the handler ref while still chaining
   * through to the production registration. The harness re-fires each
   * handler via `sw.evaluate(() => globalThis.__mokoshTest!.handlers.onStartup!())`.
   *
   * Null values indicate the corresponding addListener never ran in
   * this isolate (e.g. notificationOnClicked may be null until the
   * production listener registration block executes).
   *
   * In the offscreen isolate, all three are null (offscreen does not
   * register these chrome.* listeners — they live in the SW).
   */
  handlers: {
    /** chrome.action.onClicked — fires on toolbar click when popup === ''. */
    onClicked: ((tab: chrome.tabs.Tab) => void | Promise<void>) | null;
    /** chrome.runtime.onStartup — fires once per browser session start. */
    onStartup: (() => void | Promise<void>) | null;
    /** chrome.notifications.onClicked — fires when operator clicks a notification. */
    notificationOnClicked: ((notificationId: string) => void | Promise<void>) | null;
  };

  /**
   * Total count of `chrome.notifications.create(...)` invocations
   * since the hook installed. Monotonic; the harness snapshots before
   * a trigger event and after the propagation wait, asserting on the
   * DELTA (not the absolute count) so retries do not invalidate
   * assertions. See Plan 01-11 RESEARCH §11 open-question resolution 2.
   *
   * Always 0 in the offscreen isolate (notifications.create is
   * SW-only in this extension).
   */
  notificationCount: number;

  /**
   * The most recent `options` argument passed to
   * `chrome.notifications.create(...)`. The harness asserts on
   * `iconUrl` shape (matches /icons\/icon(?:128|48)\.png$/) and on
   * `title` strings (e.g. 'Mokosh ready').
   *
   * Generic argument: chrome.notifications.NotificationOptions is
   * declared with a `<true | false>` generic distinguishing the
   * "create" shape (all fields required — true) from the "update"
   * shape (all fields optional — false). Our production code always
   * uses the create shape; we widen here to `NotificationOptions<true>`
   * so the harness can read iconUrl etc. as definitely-present.
   *
   * Null until the first notification fires.
   */
  lastNotificationOptions: chrome.notifications.NotificationOptions<true> | null;

  /**
   * IDs of every notification created since hook install, in invocation
   * order. The harness asserts ID-prefix membership (e.g. an id
   * starting 'mokosh-startup-' MUST appear after fireOnStartup; an id
   * starting 'mokosh-recovery-' MUST appear after RECORDING_ERROR
   * other than 'user-stopped-sharing'). Defense in depth alongside
   * notificationCount per RESEARCH §11 resolution 2.
   *
   * Returned as a defensive copy from the getter — mutating the
   * returned array does not affect the underlying record.
   */
  readonly notificationIds: ReadonlyArray<string>;

  /**
   * Read the currently-active MediaStream in the offscreen isolate, if
   * any. Returns null when not recording. Used by the harness to read
   * `getVideoTracks()[0].getSettings().displaySurface` (assertion 3)
   * and to dispatch the 'ended' event on the track (assertion 6 — the
   * Bug B simulation per RESEARCH §7 BLOCKER).
   *
   * Always undefined in the SW isolate; runtime null in the offscreen
   * isolate signals "not currently recording" (the harness MUST start
   * a recording before invoking this — see assertion 6 ordering).
   */
  getCurrentStream?: () => MediaStream | null;

  /**
   * Read the current count of segments in the offscreen recorder's
   * ring buffer (the `segments: Blob[]` module-level array in
   * src/offscreen/recorder.ts:62). Used by assertion 11 to verify
   * the 30s window per D-13 (3 × 10s segments).
   *
   * Always undefined in the SW isolate.
   */
  getSegmentCount?: () => number;
}

declare global {
  // The eslint disable directive mirrors the pattern in @types/chrome's
  // ambient declarations; `var` is the only TS syntax that augments
  // globalThis cleanly without a side-effecting `Window` augmentation
  // (which would not apply in SW/offscreen contexts).
  // eslint-disable-next-line no-var
  var __mokoshTest: MokoshTestSurface | undefined;
}

export {};
