import test from "node:test";
import assert from "node:assert/strict";
import { ConfiguredShortcuts } from "../utils/shortcut-config";
import {
  createOverlayShortcutRuntimeHandlers,
  OverlayShortcutRuntimeDeps,
  runOverlayShortcutLocalFallback,
} from "./overlay-shortcut-handler";

function makeShortcuts(
  overrides: Partial<ConfiguredShortcuts> = {},
): ConfiguredShortcuts {
  return {
    toggleVisibleOverlayGlobal: null,
    toggleInvisibleOverlayGlobal: null,
    copySubtitle: null,
    copySubtitleMultiple: null,
    updateLastCardFromClipboard: null,
    triggerFieldGrouping: null,
    triggerSubsync: null,
    mineSentence: null,
    mineSentenceMultiple: null,
    multiCopyTimeoutMs: 2500,
    toggleSecondarySub: null,
    markAudioCard: null,
    openRuntimeOptions: null,
    openJimaku: null,
    ...overrides,
  };
}

function createDeps(overrides: Partial<OverlayShortcutRuntimeDeps> = {}) {
  const calls: string[] = [];
  const osd: string[] = [];
  const deps: OverlayShortcutRuntimeDeps = {
    showMpvOsd: (text) => {
      osd.push(text);
    },
    openRuntimeOptions: () => {
      calls.push("openRuntimeOptions");
    },
    openJimaku: () => {
      calls.push("openJimaku");
    },
    markAudioCard: async () => {
      calls.push("markAudioCard");
    },
    copySubtitleMultiple: (timeoutMs) => {
      calls.push(`copySubtitleMultiple:${timeoutMs}`);
    },
    copySubtitle: () => {
      calls.push("copySubtitle");
    },
    toggleSecondarySub: () => {
      calls.push("toggleSecondarySub");
    },
    updateLastCardFromClipboard: async () => {
      calls.push("updateLastCardFromClipboard");
    },
    triggerFieldGrouping: async () => {
      calls.push("triggerFieldGrouping");
    },
    triggerSubsync: async () => {
      calls.push("triggerSubsync");
    },
    mineSentence: async () => {
      calls.push("mineSentence");
    },
    mineSentenceMultiple: (timeoutMs) => {
      calls.push(`mineSentenceMultiple:${timeoutMs}`);
    },
    ...overrides,
  };

  return { deps, calls, osd };
}

test("createOverlayShortcutRuntimeHandlers dispatches sync and async handlers", async () => {
  const { deps, calls } = createDeps();
  const { overlayHandlers, fallbackHandlers } =
    createOverlayShortcutRuntimeHandlers(deps);

  overlayHandlers.copySubtitle();
  overlayHandlers.copySubtitleMultiple(1111);
  overlayHandlers.toggleSecondarySub();
  overlayHandlers.openRuntimeOptions();
  overlayHandlers.openJimaku();
  overlayHandlers.mineSentenceMultiple(2222);
  overlayHandlers.updateLastCardFromClipboard();
  fallbackHandlers.mineSentence();
  await new Promise((resolve) => setImmediate(resolve));

  assert.deepEqual(calls, [
    "copySubtitle",
    "copySubtitleMultiple:1111",
    "toggleSecondarySub",
    "openRuntimeOptions",
    "openJimaku",
    "mineSentenceMultiple:2222",
    "updateLastCardFromClipboard",
    "mineSentence",
  ]);
});

test("createOverlayShortcutRuntimeHandlers reports async failures via OSD", async () => {
  const logs: unknown[][] = [];
  const originalError = console.error;
  console.error = (...args: unknown[]) => {
    logs.push(args);
  };

  try {
    const { deps, osd } = createDeps({
      markAudioCard: async () => {
        throw new Error("audio boom");
      },
    });
    const { overlayHandlers } = createOverlayShortcutRuntimeHandlers(deps);

    overlayHandlers.markAudioCard();
    await new Promise((resolve) => setImmediate(resolve));

    assert.equal(logs.length, 1);
    assert.equal(typeof logs[0]?.[0], "string");
    assert.ok(String(logs[0]?.[0]).includes("markLastCardAsAudioCard failed:"));
    assert.ok(
      String(logs[0]?.[0]).includes("audio boom"),
    );
    assert.ok(osd.some((entry) => entry.includes("Audio card failed: audio boom")));
  } finally {
    console.error = originalError;
  }
});

test("runOverlayShortcutLocalFallback dispatches matching actions with timeout", () => {
  const handled: string[] = [];
  const matched: Array<{ accelerator: string; allowWhenRegistered: boolean }> = [];
  const shortcuts = makeShortcuts({
    copySubtitleMultiple: "Ctrl+M",
    multiCopyTimeoutMs: 4321,
  });

  const result = runOverlayShortcutLocalFallback(
    {} as Electron.Input,
    shortcuts,
    (_input, accelerator, allowWhenRegistered) => {
      matched.push({
        accelerator,
        allowWhenRegistered: allowWhenRegistered === true,
      });
      return accelerator === "Ctrl+M";
    },
    {
      openRuntimeOptions: () => handled.push("openRuntimeOptions"),
      openJimaku: () => handled.push("openJimaku"),
      markAudioCard: () => handled.push("markAudioCard"),
      copySubtitleMultiple: (timeoutMs) =>
        handled.push(`copySubtitleMultiple:${timeoutMs}`),
      copySubtitle: () => handled.push("copySubtitle"),
      toggleSecondarySub: () => handled.push("toggleSecondarySub"),
      updateLastCardFromClipboard: () =>
        handled.push("updateLastCardFromClipboard"),
      triggerFieldGrouping: () => handled.push("triggerFieldGrouping"),
      triggerSubsync: () => handled.push("triggerSubsync"),
      mineSentence: () => handled.push("mineSentence"),
      mineSentenceMultiple: (timeoutMs) =>
        handled.push(`mineSentenceMultiple:${timeoutMs}`),
    },
  );

  assert.equal(result, true);
  assert.deepEqual(handled, ["copySubtitleMultiple:4321"]);
  assert.deepEqual(matched, [{ accelerator: "Ctrl+M", allowWhenRegistered: false }]);
});

test("runOverlayShortcutLocalFallback passes allowWhenRegistered for secondary-sub toggle", () => {
  const matched: Array<{ accelerator: string; allowWhenRegistered: boolean }> = [];
  const shortcuts = makeShortcuts({
    toggleSecondarySub: "Ctrl+2",
  });

  const result = runOverlayShortcutLocalFallback(
    {} as Electron.Input,
    shortcuts,
    (_input, accelerator, allowWhenRegistered) => {
      matched.push({
        accelerator,
        allowWhenRegistered: allowWhenRegistered === true,
      });
      return accelerator === "Ctrl+2";
    },
    {
      openRuntimeOptions: () => {},
      openJimaku: () => {},
      markAudioCard: () => {},
      copySubtitleMultiple: () => {},
      copySubtitle: () => {},
      toggleSecondarySub: () => {},
      updateLastCardFromClipboard: () => {},
      triggerFieldGrouping: () => {},
      triggerSubsync: () => {},
      mineSentence: () => {},
      mineSentenceMultiple: () => {},
    },
  );

  assert.equal(result, true);
  assert.deepEqual(matched, [{ accelerator: "Ctrl+2", allowWhenRegistered: true }]);
});

test("runOverlayShortcutLocalFallback allows registered-global jimaku shortcut", () => {
  const matched: Array<{ accelerator: string; allowWhenRegistered: boolean }> = [];
  const shortcuts = makeShortcuts({
    openJimaku: "Ctrl+J",
  });

  const result = runOverlayShortcutLocalFallback(
    {} as Electron.Input,
    shortcuts,
    (_input, accelerator, allowWhenRegistered) => {
      matched.push({
        accelerator,
        allowWhenRegistered: allowWhenRegistered === true,
      });
      return accelerator === "Ctrl+J";
    },
    {
      openRuntimeOptions: () => {},
      openJimaku: () => {},
      markAudioCard: () => {},
      copySubtitleMultiple: () => {},
      copySubtitle: () => {},
      toggleSecondarySub: () => {},
      updateLastCardFromClipboard: () => {},
      triggerFieldGrouping: () => {},
      triggerSubsync: () => {},
      mineSentence: () => {},
      mineSentenceMultiple: () => {},
    },
  );

  assert.equal(result, true);
  assert.deepEqual(matched, [{ accelerator: "Ctrl+J", allowWhenRegistered: true }]);
});

test("runOverlayShortcutLocalFallback returns false when no action matches", () => {
  const shortcuts = makeShortcuts({
    copySubtitle: "Ctrl+C",
  });
  let called = false;

  const result = runOverlayShortcutLocalFallback(
    {} as Electron.Input,
    shortcuts,
    () => false,
    {
      openRuntimeOptions: () => {
        called = true;
      },
      openJimaku: () => {
        called = true;
      },
      markAudioCard: () => {
        called = true;
      },
      copySubtitleMultiple: () => {
        called = true;
      },
      copySubtitle: () => {
        called = true;
      },
      toggleSecondarySub: () => {
        called = true;
      },
      updateLastCardFromClipboard: () => {
        called = true;
      },
      triggerFieldGrouping: () => {
        called = true;
      },
      triggerSubsync: () => {
        called = true;
      },
      mineSentence: () => {
        called = true;
      },
      mineSentenceMultiple: () => {
        called = true;
      },
    },
  );

  assert.equal(result, false);
  assert.equal(called, false);
});
