import { describe, it, expect, vi } from "vitest";
import { installRuntimeControlBridge } from "./bridge";

function createMockDeps() {
  return {
    onPlay: vi.fn(),
    onPause: vi.fn(),
    onSeek: vi.fn(),
    onTick: vi.fn(),
    onSetMuted: vi.fn(),
    onSetVolume: vi.fn(),
    onSetMediaOutputMuted: vi.fn(),
    onSetPlaybackRate: vi.fn(),
    onEnablePickMode: vi.fn(),
    onDisablePickMode: vi.fn(),
  };
}

function makeControlMessage(action: string, extra?: Record<string, unknown>) {
  return new MessageEvent("message", {
    data: { source: "hf-parent", type: "control", action, ...extra },
  });
}

describe("installRuntimeControlBridge", () => {
  it("dispatches play command", () => {
    const deps = createMockDeps();
    const handler = installRuntimeControlBridge(deps);
    handler(makeControlMessage("play"));
    expect(deps.onPlay).toHaveBeenCalledOnce();
  });

  it("dispatches pause command", () => {
    const deps = createMockDeps();
    const handler = installRuntimeControlBridge(deps);
    handler(makeControlMessage("pause"));
    expect(deps.onPause).toHaveBeenCalledOnce();
  });

  it("dispatches seek command with frame and mode", () => {
    const deps = createMockDeps();
    const handler = installRuntimeControlBridge(deps);
    handler(makeControlMessage("seek", { frame: 150, seekMode: "drag" }));
    expect(deps.onSeek).toHaveBeenCalledWith(150, "drag");
  });

  it("seek defaults frame to 0 and seekMode to commit", () => {
    const deps = createMockDeps();
    const handler = installRuntimeControlBridge(deps);
    handler(makeControlMessage("seek"));
    expect(deps.onSeek).toHaveBeenCalledWith(0, "commit");
  });

  it("dispatches set-muted command", () => {
    const deps = createMockDeps();
    const handler = installRuntimeControlBridge(deps);
    handler(makeControlMessage("set-muted", { muted: true }));
    expect(deps.onSetMuted).toHaveBeenCalledWith(true);
  });

  it("dispatches set-volume command", () => {
    const deps = createMockDeps();
    const handler = installRuntimeControlBridge(deps);
    handler(makeControlMessage("set-volume", { volume: 0.5 }));
    expect(deps.onSetVolume).toHaveBeenCalledWith(0.5);
  });

  it("clamps set-volume to [0, 1]", () => {
    const deps = createMockDeps();
    const handler = installRuntimeControlBridge(deps);
    handler(makeControlMessage("set-volume", { volume: 1.5 }));
    expect(deps.onSetVolume).toHaveBeenCalledWith(1);
    handler(makeControlMessage("set-volume", { volume: -0.5 }));
    expect(deps.onSetVolume).toHaveBeenCalledWith(0);
  });

  it("defaults volume to 1 when absent", () => {
    const deps = createMockDeps();
    const handler = installRuntimeControlBridge(deps);
    handler(makeControlMessage("set-volume"));
    expect(deps.onSetVolume).toHaveBeenCalledWith(1);
  });

  it("dispatches set-media-output-muted command", () => {
    const deps = createMockDeps();
    const handler = installRuntimeControlBridge(deps);
    handler(makeControlMessage("set-media-output-muted", { muted: true }));
    expect(deps.onSetMediaOutputMuted).toHaveBeenCalledWith(true);
    handler(makeControlMessage("set-media-output-muted", { muted: false }));
    expect(deps.onSetMediaOutputMuted).toHaveBeenCalledWith(false);
  });

  it("set-media-output-muted coerces absent flag to false", () => {
    const deps = createMockDeps();
    const handler = installRuntimeControlBridge(deps);
    handler(makeControlMessage("set-media-output-muted"));
    expect(deps.onSetMediaOutputMuted).toHaveBeenCalledWith(false);
  });

  it("dispatches set-playback-rate command", () => {
    const deps = createMockDeps();
    const handler = installRuntimeControlBridge(deps);
    handler(makeControlMessage("set-playback-rate", { playbackRate: 2 }));
    expect(deps.onSetPlaybackRate).toHaveBeenCalledWith(2);
  });

  it("defaults playbackRate to 1", () => {
    const deps = createMockDeps();
    const handler = installRuntimeControlBridge(deps);
    handler(makeControlMessage("set-playback-rate"));
    expect(deps.onSetPlaybackRate).toHaveBeenCalledWith(1);
  });

  it("dispatches tick command", () => {
    const deps = createMockDeps();
    const handler = installRuntimeControlBridge(deps);
    handler(makeControlMessage("tick"));
    expect(deps.onTick).toHaveBeenCalledOnce();
  });

  it("dispatches enable-pick-mode", () => {
    const deps = createMockDeps();
    const handler = installRuntimeControlBridge(deps);
    handler(makeControlMessage("enable-pick-mode"));
    expect(deps.onEnablePickMode).toHaveBeenCalledOnce();
  });

  it("dispatches disable-pick-mode", () => {
    const deps = createMockDeps();
    const handler = installRuntimeControlBridge(deps);
    handler(makeControlMessage("disable-pick-mode"));
    expect(deps.onDisablePickMode).toHaveBeenCalledOnce();
  });

  it("ignores messages from wrong source", () => {
    const deps = createMockDeps();
    const handler = installRuntimeControlBridge(deps);
    handler(
      new MessageEvent("message", {
        data: { source: "other", type: "control", action: "play" },
      }),
    );
    expect(deps.onPlay).not.toHaveBeenCalled();
  });

  it("ignores messages with wrong type", () => {
    const deps = createMockDeps();
    const handler = installRuntimeControlBridge(deps);
    handler(
      new MessageEvent("message", {
        data: { source: "hf-parent", type: "state", action: "play" },
      }),
    );
    expect(deps.onPlay).not.toHaveBeenCalled();
  });

  it("ignores null data", () => {
    const deps = createMockDeps();
    const handler = installRuntimeControlBridge(deps);
    handler(new MessageEvent("message", { data: null }));
    expect(deps.onPlay).not.toHaveBeenCalled();
  });

  it("handles flash-elements command without crashing", () => {
    const deps = createMockDeps();
    const handler = installRuntimeControlBridge(deps);
    expect(() =>
      handler(makeControlMessage("flash-elements", { selectors: [".test"], duration: 500 })),
    ).not.toThrow();
  });
});
