/**
 * Transport authority: play/pause/toggle are DECIDED in Rust, never in the webview.
 *
 * TRACES: UR-005 | DR-097 | UT-091
 *
 * The frontend used to short-circuit transport controls whenever a video adapter
 * was registered: `toggle()` read `el.paused` off the DOM and flipped the element
 * directly, so the Rust `PlayerController` never saw the intent and could not
 * serialise competing ones. Because `el.paused` flips transiently while an HTML5
 * element buffers or settles a seek, two intents arriving ~150ms apart could read
 * *different* values and perform *opposing* actions — one playing, one pausing —
 * which is the self-sustaining play/pause loop observed on Android.
 *
 * The rule these tests pin: a transport intent always reaches the backend. Rust
 * decides play-vs-pause from controller state and drives the webview element back
 * through a ControlCommand event (the same "backend decides, adapter executes"
 * split `player_seek_video` already uses).
 */

import { describe, it, expect, vi, beforeEach } from "vitest";

const mockCommands = {
  playerPlay: vi.fn(async () => ({})),
  playerPause: vi.fn(async () => ({})),
  playerToggle: vi.fn(async () => ({})),
  playerStop: vi.fn(async () => ({})),
};

vi.mock("$lib/api/bindings", () => ({
  commands: mockCommands,
  // Stores pulled in transitively subscribe to typed events at module load.
  events: {
    playerStatusEvent: { listen: vi.fn(async () => () => {}) },
    downloadEvent: { listen: vi.fn(async () => () => {}) },
    searchEvent: { listen: vi.fn(async () => () => {}) },
  },
}));

vi.mock("$lib/stores/auth", () => ({
  auth: {
    subscribe: (fn: (v: unknown) => void) => {
      fn({ isAuthenticated: true });
      return () => {};
    },
    getRepository: () => ({ getHandle: () => "handle-1" }),
  },
}));

/** A video adapter that records whether the facade reached into it directly. */
function makeAdapter() {
  return {
    kind: "html5" as const,
    play: vi.fn(async () => {}),
    pause: vi.fn(async () => {}),
    toggle: vi.fn(async () => true),
    seekElement: vi.fn(async () => {}),
    reloadSource: vi.fn(async () => {}),
    attach: vi.fn(),
    dispose: vi.fn(async () => {}),
    setVolume: vi.fn(),
    setMuted: vi.fn(),
    selectSubtitle: vi.fn(async () => {}),
    getPosition: vi.fn(() => 0),
    load: vi.fn(async () => {}),
  };
}

describe("transport authority lives in Rust", () => {
  let playerController: any;
  let adapter: ReturnType<typeof makeAdapter>;

  beforeEach(async () => {
    vi.clearAllMocks();
    vi.resetModules();
    ({ playerController } = await import("./index"));
    adapter = makeAdapter();
    playerController.setActiveAdapter(adapter);
  });

  it("routes toggle to the backend even when a video adapter is active", async () => {
    await playerController.toggle();

    expect(mockCommands.playerToggle).toHaveBeenCalledTimes(1);
    // The webview must NOT decide play-vs-pause from the DOM.
    expect(adapter.toggle).not.toHaveBeenCalled();
  });

  it("routes play to the backend even when a video adapter is active", async () => {
    await playerController.play();

    expect(mockCommands.playerPlay).toHaveBeenCalledTimes(1);
    expect(adapter.play).not.toHaveBeenCalled();
  });

  it("routes pause to the backend even when a video adapter is active", async () => {
    await playerController.pause();

    expect(mockCommands.playerPause).toHaveBeenCalledTimes(1);
    expect(adapter.pause).not.toHaveBeenCalled();
  });

  it("still routes transport to the backend with no adapter (audio path unchanged)", async () => {
    playerController.clearActiveAdapter();

    await playerController.toggle();
    await playerController.play();
    await playerController.pause();

    expect(mockCommands.playerToggle).toHaveBeenCalledTimes(1);
    expect(mockCommands.playerPlay).toHaveBeenCalledTimes(1);
    expect(mockCommands.playerPause).toHaveBeenCalledTimes(1);
  });
});
