import assert from 'node:assert/strict';
import test from 'node:test';

import type { CharacterDictionarySelectionSnapshot, ElectronAPI } from '../../types';
import { createRendererState } from '../state.js';
import { createCharacterDictionaryModal } from './character-dictionary.js';

function createClassList(initialTokens: string[] = []) {
  const tokens = new Set(initialTokens);
  return {
    add: (...entries: string[]) => entries.forEach((entry) => tokens.add(entry)),
    remove: (...entries: string[]) => entries.forEach((entry) => tokens.delete(entry)),
    toggle: (entry: string, force?: boolean) => {
      if (force === undefined) {
        if (tokens.has(entry)) tokens.delete(entry);
        else tokens.add(entry);
        return;
      }
      if (force) tokens.add(entry);
      else tokens.delete(entry);
    },
    contains: (entry: string) => tokens.has(entry),
  };
}

function createElementStub() {
  const listeners = new Map<string, Array<(event?: { stopPropagation?: () => void }) => void>>();
  return {
    className: '',
    textContent: '',
    type: '',
    value: '',
    disabled: false,
    children: [] as unknown[],
    classList: createClassList(),
    append(...children: unknown[]) {
      this.children.push(...children);
    },
    addEventListener: (
      event: string,
      listener: (event?: { stopPropagation?: () => void }) => void,
    ) => {
      listeners.set(event, [...(listeners.get(event) ?? []), listener]);
    },
    dispatchEvent: (event: string, payload?: { stopPropagation?: () => void }) => {
      for (const listener of listeners.get(event) ?? []) listener(payload);
    },
  };
}

function createNodeStub(hidden = false) {
  const listeners = new Map<string, Array<(event?: { preventDefault?: () => void }) => void>>();
  return {
    textContent: '',
    value: '',
    disabled: false,
    children: [] as unknown[],
    classList: createClassList(hidden ? ['hidden'] : []),
    setAttribute: () => {},
    addEventListener: (
      event: string,
      listener: (event?: { preventDefault?: () => void }) => void,
    ) => {
      listeners.set(event, [...(listeners.get(event) ?? []), listener]);
    },
    dispatchEvent: (event: string, payload?: { preventDefault?: () => void }) => {
      for (const listener of listeners.get(event) ?? []) listener(payload);
    },
    append(...children: unknown[]) {
      this.children.push(...children);
    },
    replaceChildren(...children: unknown[]) {
      this.children = [...children];
    },
  };
}

function flushAsyncWork(): Promise<void> {
  return new Promise((resolve) => {
    setTimeout(resolve, 0);
  });
}

test('character dictionary modal announces open before AniList refresh resolves', async () => {
  const previousWindow = globalThis.window;
  const previousDocument = globalThis.document;
  let resolveSelection: (snapshot: CharacterDictionarySelectionSnapshot) => void = () => {};
  const selectionPromise = new Promise<CharacterDictionarySelectionSnapshot>((resolve) => {
    resolveSelection = resolve;
  });
  const events: string[] = [];
  const overlay = createNodeStub();
  const modalNode = createNodeStub(true);
  const state = createRendererState();

  Object.defineProperty(globalThis, 'window', {
    configurable: true,
    value: {
      electronAPI: {
        getCharacterDictionarySelection: () => selectionPromise,
        setCharacterDictionarySelection: async () => ({
          ok: false,
          seriesKey: 'test',
          selected: { id: 0, title: '', episodes: null },
          staleMediaIds: [],
        }),
        notifyOverlayModalClosed: () => {},
        notifyOverlayModalOpened: (modal: string) => {
          events.push(`notify:${modal}`);
        },
      } satisfies Pick<
        ElectronAPI,
        | 'getCharacterDictionarySelection'
        | 'setCharacterDictionarySelection'
        | 'notifyOverlayModalClosed'
        | 'notifyOverlayModalOpened'
      >,
    },
  });
  Object.defineProperty(globalThis, 'document', {
    configurable: true,
    value: {
      createElement: () => createElementStub(),
    },
  });

  try {
    const modal = createCharacterDictionaryModal(
      {
        state,
        dom: {
          overlay,
          characterDictionaryModal: modalNode,
          characterDictionaryClose: createNodeStub(),
          characterDictionarySummary: createNodeStub(),
          characterDictionaryCurrent: createNodeStub(),
          characterDictionaryCandidates: createNodeStub(),
          characterDictionaryStatus: createNodeStub(),
        },
      } as never,
      {
        modalStateReader: { isAnyModalOpen: () => false },
        syncSettingsModalSubtitleSuppression: () => {
          events.push('sync-subtitle-suppression');
        },
      },
    );

    const openPromise = modal.openCharacterDictionaryModal();

    assert.equal(state.characterDictionaryModalOpen, true);
    assert.equal(modalNode.classList.contains('hidden'), false);
    assert.deepEqual(events, ['sync-subtitle-suppression', 'notify:character-dictionary']);

    resolveSelection({
      seriesKey: 'tower-of-god-2020',
      guessTitle: 'Tower of God',
      current: null,
      override: null,
      candidates: [{ id: 115230, title: 'Tower of God', episodes: 13 }],
    });
    await openPromise;
  } finally {
    Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow });
    Object.defineProperty(globalThis, 'document', { configurable: true, value: previousDocument });
  }
});

test('character dictionary modal opens manager view with active entries', async () => {
  const previousWindow = globalThis.window;
  const previousDocument = globalThis.document;
  const calls: string[] = [];
  const overlay = createNodeStub();
  const modalNode = createNodeStub(true);
  const managedEntries = createNodeStub();
  const summary = createNodeStub();
  const state = createRendererState();

  Object.defineProperty(globalThis, 'window', {
    configurable: true,
    value: {
      electronAPI: {
        getCharacterDictionaryManagerSnapshot: async () => {
          calls.push('snapshot');
          return {
            entries: [
              { mediaId: 21202, label: '21202 - KonoSuba', title: 'KonoSuba', current: true },
              {
                mediaId: 115230,
                label: '115230 - Tower of God',
                title: 'Tower of God',
                current: false,
              },
            ],
          };
        },
        removeCharacterDictionaryManagedEntry: async () => ({ ok: true, entries: [] }),
        moveCharacterDictionaryManagedEntry: async () => ({ ok: true, entries: [] }),
        getCharacterDictionarySelection: async () => ({
          seriesKey: '',
          guessTitle: null,
          current: null,
          override: null,
          candidates: [],
        }),
        setCharacterDictionarySelection: async () => ({
          ok: false,
          seriesKey: '',
          selected: { id: 0, title: '', episodes: null },
          staleMediaIds: [],
        }),
        notifyOverlayModalClosed: () => {},
        notifyOverlayModalOpened: () => {},
      } as never,
    },
  });
  Object.defineProperty(globalThis, 'document', {
    configurable: true,
    value: {
      createElement: () => createElementStub(),
    },
  });

  try {
    const modal = createCharacterDictionaryModal(
      {
        state,
        dom: {
          overlay,
          characterDictionaryModal: modalNode,
          characterDictionaryClose: createNodeStub(),
          characterDictionarySummary: summary,
          characterDictionaryCurrent: createNodeStub(),
          characterDictionarySearchInput: createNodeStub(),
          characterDictionarySearchButton: createNodeStub(),
          characterDictionaryCandidates: createNodeStub(),
          characterDictionaryStatus: createNodeStub(),
          characterDictionarySearchPanel: createNodeStub(),
          characterDictionaryManagerPanel: createNodeStub(true),
          characterDictionaryOverrideTab: createNodeStub(),
          characterDictionaryManageTab: createNodeStub(),
          characterDictionaryManagedEntries: managedEntries,
        },
      } as never,
      {
        modalStateReader: { isAnyModalOpen: () => false },
        syncSettingsModalSubtitleSuppression: () => {},
      },
    ) as ReturnType<typeof createCharacterDictionaryModal> & {
      openCharacterDictionaryManagerModal: () => Promise<void>;
    };

    await modal.openCharacterDictionaryManagerModal();

    assert.equal(state.characterDictionaryModalOpen, true);
    assert.deepEqual(calls, ['snapshot']);
    assert.equal(managedEntries.children.length, 2);
    assert.equal(
      summary.textContent,
      '2 loaded character dictionaries. Order controls eviction priority; current dictionary stays loaded.',
    );
  } finally {
    Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow });
    Object.defineProperty(globalThis, 'document', { configurable: true, value: previousDocument });
  }
});

test('character dictionary manager reports failed reorder IPC calls', async () => {
  const previousWindow = globalThis.window;
  const previousDocument = globalThis.document;
  const overlay = createNodeStub();
  const modalNode = createNodeStub(true);
  const managedEntries = createNodeStub();
  const status = createNodeStub();
  const state = createRendererState();

  Object.defineProperty(globalThis, 'window', {
    configurable: true,
    value: {
      electronAPI: {
        getCharacterDictionaryManagerSnapshot: async () => ({
          entries: [
            { mediaId: 21202, label: '21202 - KonoSuba', title: 'KonoSuba', current: true },
            {
              mediaId: 115230,
              label: '115230 - Tower of God',
              title: 'Tower of God',
              current: false,
            },
          ],
        }),
        moveCharacterDictionaryManagedEntry: async () => {
          throw new Error('move failed');
        },
        removeCharacterDictionaryManagedEntry: async () => ({ ok: true, entries: [] }),
        getCharacterDictionarySelection: async () => ({
          seriesKey: '',
          guessTitle: null,
          current: null,
          override: null,
          candidates: [],
        }),
        setCharacterDictionarySelection: async () => ({
          ok: false,
          seriesKey: '',
          selected: { id: 0, title: '', episodes: null },
          staleMediaIds: [],
        }),
        notifyOverlayModalClosed: () => {},
        notifyOverlayModalOpened: () => {},
      } as never,
    },
  });
  Object.defineProperty(globalThis, 'document', {
    configurable: true,
    value: {
      createElement: () => createElementStub(),
    },
  });

  try {
    const modal = createCharacterDictionaryModal(
      {
        state,
        dom: {
          overlay,
          characterDictionaryModal: modalNode,
          characterDictionaryClose: createNodeStub(),
          characterDictionarySummary: createNodeStub(),
          characterDictionaryCurrent: createNodeStub(),
          characterDictionarySearchInput: createNodeStub(),
          characterDictionarySearchButton: createNodeStub(),
          characterDictionaryCandidates: createNodeStub(),
          characterDictionaryStatus: status,
          characterDictionarySearchPanel: createNodeStub(),
          characterDictionaryManagerPanel: createNodeStub(true),
          characterDictionaryOverrideTab: createNodeStub(),
          characterDictionaryManageTab: createNodeStub(),
          characterDictionaryManagedEntries: managedEntries,
        },
      } as never,
      {
        modalStateReader: { isAnyModalOpen: () => false },
        syncSettingsModalSubtitleSuppression: () => {},
      },
    );

    await modal.openCharacterDictionaryManagerModal();
    const secondEntry = managedEntries.children[1] as { children: unknown[] };
    const controls = secondEntry.children[1] as {
      children: Array<{ dispatchEvent: (event: string, payload?: unknown) => void }>;
    };
    controls.children[0]?.dispatchEvent('click', { stopPropagation: () => {} });
    await flushAsyncWork();

    assert.equal(status.textContent, 'move failed');
  } finally {
    Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow });
    Object.defineProperty(globalThis, 'document', { configurable: true, value: previousDocument });
  }
});

test('character dictionary manager reports pending refresh after removal', async () => {
  const previousWindow = globalThis.window;
  const previousDocument = globalThis.document;
  const overlay = createNodeStub();
  const modalNode = createNodeStub(true);
  const managedEntries = createNodeStub();
  const status = createNodeStub();
  const state = createRendererState();

  Object.defineProperty(globalThis, 'window', {
    configurable: true,
    value: {
      electronAPI: {
        getCharacterDictionaryManagerSnapshot: async () => ({
          entries: [
            { mediaId: 21202, label: '21202 - KonoSuba', title: 'KonoSuba', current: true },
            {
              mediaId: 115230,
              label: '115230 - Tower of God',
              title: 'Tower of God',
              current: false,
            },
          ],
        }),
        moveCharacterDictionaryManagedEntry: async () => ({ ok: true, entries: [] }),
        removeCharacterDictionaryManagedEntry: async () => ({
          ok: true,
          entries: [
            { mediaId: 21202, label: '21202 - KonoSuba', title: 'KonoSuba', current: true },
          ],
          rebuildRequired: true,
        }),
        getCharacterDictionarySelection: async () => ({
          seriesKey: '',
          guessTitle: null,
          current: null,
          override: null,
          candidates: [],
        }),
        setCharacterDictionarySelection: async () => ({
          ok: false,
          seriesKey: '',
          selected: { id: 0, title: '', episodes: null },
          staleMediaIds: [],
        }),
        notifyOverlayModalClosed: () => {},
        notifyOverlayModalOpened: () => {},
      } as never,
    },
  });
  Object.defineProperty(globalThis, 'document', {
    configurable: true,
    value: {
      createElement: () => createElementStub(),
    },
  });

  try {
    const modal = createCharacterDictionaryModal(
      {
        state,
        dom: {
          overlay,
          characterDictionaryModal: modalNode,
          characterDictionaryClose: createNodeStub(),
          characterDictionarySummary: createNodeStub(),
          characterDictionaryCurrent: createNodeStub(),
          characterDictionarySearchInput: createNodeStub(),
          characterDictionarySearchButton: createNodeStub(),
          characterDictionaryCandidates: createNodeStub(),
          characterDictionaryStatus: status,
          characterDictionarySearchPanel: createNodeStub(),
          characterDictionaryManagerPanel: createNodeStub(true),
          characterDictionaryOverrideTab: createNodeStub(),
          characterDictionaryManageTab: createNodeStub(),
          characterDictionaryManagedEntries: managedEntries,
        },
      } as never,
      {
        modalStateReader: { isAnyModalOpen: () => false },
        syncSettingsModalSubtitleSuppression: () => {},
      },
    );

    await modal.openCharacterDictionaryManagerModal();
    const secondEntry = managedEntries.children[1] as { children: unknown[] };
    const controls = secondEntry.children[1] as {
      children: Array<{ dispatchEvent: (event: string, payload?: unknown) => void }>;
    };
    controls.children[3]?.dispatchEvent('click', { stopPropagation: () => {} });
    await flushAsyncWork();

    assert.equal(status.textContent, 'Entry removed. Merged dictionary will refresh shortly.');
  } finally {
    Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow });
    Object.defineProperty(globalThis, 'document', { configurable: true, value: previousDocument });
  }
});

test('character dictionary modal loads candidates and applies selected override', async () => {
  const previousWindow = globalThis.window;
  const previousDocument = globalThis.document;
  const snapshot: CharacterDictionarySelectionSnapshot = {
    seriesKey: 're-zero-starting-life-in-another-world-2016',
    guessTitle: 'Re ZERO, Starting Life in Another World',
    current: { id: 10607, title: 'Rerere no Tensai Bakabon', episodes: 24 },
    override: null,
    candidates: [{ id: 21355, title: 'Re:ZERO -Starting Life in Another World-', episodes: 25 }],
  };
  const calls: number[] = [];
  const overlay = createNodeStub();
  const modalNode = createNodeStub(true);
  const closeButton = createNodeStub();
  const candidates = createNodeStub();
  const status = createNodeStub();
  const state = createRendererState();

  Object.defineProperty(globalThis, 'window', {
    configurable: true,
    value: {
      electronAPI: {
        getCharacterDictionarySelection: async () => snapshot,
        setCharacterDictionarySelection: async (mediaId: number) => {
          calls.push(mediaId);
          return {
            ok: true,
            seriesKey: snapshot.seriesKey,
            selected: snapshot.candidates[0]!,
            staleMediaIds: [10607],
          };
        },
        notifyOverlayModalClosed: () => {},
        notifyOverlayModalOpened: () => {},
      } satisfies Pick<
        ElectronAPI,
        | 'getCharacterDictionarySelection'
        | 'setCharacterDictionarySelection'
        | 'notifyOverlayModalClosed'
        | 'notifyOverlayModalOpened'
      >,
    },
  });
  Object.defineProperty(globalThis, 'document', {
    configurable: true,
    value: {
      createElement: () => createElementStub(),
    },
  });

  try {
    const modal = createCharacterDictionaryModal(
      {
        state,
        dom: {
          overlay,
          characterDictionaryModal: modalNode,
          characterDictionaryClose: closeButton,
          characterDictionarySummary: createNodeStub(),
          characterDictionaryCurrent: createNodeStub(),
          characterDictionarySearchInput: createNodeStub(),
          characterDictionarySearchButton: createNodeStub(),
          characterDictionaryCandidates: candidates,
          characterDictionaryStatus: status,
        },
      } as never,
      {
        modalStateReader: { isAnyModalOpen: () => false },
        syncSettingsModalSubtitleSuppression: () => {},
      },
    );
    modal.wireDomEvents();

    await modal.openCharacterDictionaryModal();
    assert.equal(state.characterDictionaryModalOpen, true);
    assert.equal(overlay.classList.contains('interactive'), true);
    assert.equal(modalNode.classList.contains('hidden'), false);
    assert.equal(candidates.children.length, 1);

    modal.handleCharacterDictionaryKeydown({
      key: 'Enter',
      preventDefault: () => {},
    } as KeyboardEvent);
    await flushAsyncWork();

    assert.deepEqual(calls, [21355]);
    assert.match(status.textContent, /Override saved/);

    closeButton.dispatchEvent('click');
    assert.equal(state.characterDictionaryModalOpen, false);
  } finally {
    Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow });
    Object.defineProperty(globalThis, 'document', { configurable: true, value: previousDocument });
  }
});

test('character dictionary modal shows refresh errors without rejecting open', async () => {
  const previousWindow = globalThis.window;
  const overlay = createNodeStub();
  const modalNode = createNodeStub(true);
  const status = createNodeStub();
  const state = createRendererState();

  Object.defineProperty(globalThis, 'window', {
    configurable: true,
    value: {
      electronAPI: {
        getCharacterDictionarySelection: async () => {
          throw new Error('candidate lookup failed');
        },
        setCharacterDictionarySelection: async () => ({
          ok: false,
          seriesKey: 'test',
          selected: { id: 0, title: '', episodes: null },
          staleMediaIds: [],
        }),
        notifyOverlayModalClosed: () => {},
        notifyOverlayModalOpened: () => {},
      } satisfies Pick<
        ElectronAPI,
        | 'getCharacterDictionarySelection'
        | 'setCharacterDictionarySelection'
        | 'notifyOverlayModalClosed'
        | 'notifyOverlayModalOpened'
      >,
    },
  });

  try {
    const modal = createCharacterDictionaryModal(
      {
        state,
        dom: {
          overlay,
          characterDictionaryModal: modalNode,
          characterDictionaryClose: createNodeStub(),
          characterDictionarySummary: createNodeStub(),
          characterDictionaryCurrent: createNodeStub(),
          characterDictionarySearchInput: createNodeStub(),
          characterDictionarySearchButton: createNodeStub(),
          characterDictionaryCandidates: createNodeStub(),
          characterDictionaryStatus: status,
        },
      } as never,
      {
        modalStateReader: { isAnyModalOpen: () => false },
        syncSettingsModalSubtitleSuppression: () => {},
      },
    );

    await modal.openCharacterDictionaryModal();

    assert.equal(state.characterDictionaryModalOpen, true);
    assert.equal(status.textContent, 'candidate lookup failed');
    assert.equal(status.classList.contains('error'), true);
  } finally {
    Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow });
  }
});

test('character dictionary modal seeds search input and waits for manual search', async () => {
  const previousWindow = globalThis.window;
  const previousDocument = globalThis.document;
  const initialSnapshot: CharacterDictionarySelectionSnapshot = {
    seriesKey: 'kage-no-jitsuryokusha-ni-naritakute-2022',
    guessTitle: 'Kage no Jitsuryokusha ni Naritakute!',
    current: null,
    override: null,
    candidates: [],
  };
  const searchedSnapshot: CharacterDictionarySelectionSnapshot = {
    ...initialSnapshot,
    candidates: [{ id: 130298, title: 'The Eminence in Shadow', episodes: 20 }],
  };
  const searches: Array<string | undefined> = [];
  const overlay = createNodeStub();
  const searchInput = createNodeStub();
  const searchButton = createNodeStub();
  const candidates = createNodeStub();
  const status = createNodeStub();
  const state = createRendererState();

  Object.defineProperty(globalThis, 'window', {
    configurable: true,
    value: {
      electronAPI: {
        getCharacterDictionarySelection: async (searchText?: string) => {
          searches.push(searchText);
          return searchText ? searchedSnapshot : initialSnapshot;
        },
        setCharacterDictionarySelection: async () => ({
          ok: true,
          seriesKey: initialSnapshot.seriesKey,
          selected: searchedSnapshot.candidates[0]!,
          staleMediaIds: [],
        }),
        notifyOverlayModalClosed: () => {},
        notifyOverlayModalOpened: () => {},
      } satisfies Pick<
        ElectronAPI,
        | 'getCharacterDictionarySelection'
        | 'setCharacterDictionarySelection'
        | 'notifyOverlayModalClosed'
        | 'notifyOverlayModalOpened'
      >,
    },
  });
  Object.defineProperty(globalThis, 'document', {
    configurable: true,
    value: {
      createElement: () => createElementStub(),
    },
  });

  try {
    const modal = createCharacterDictionaryModal(
      {
        state,
        dom: {
          overlay,
          characterDictionaryModal: createNodeStub(true),
          characterDictionaryClose: createNodeStub(),
          characterDictionarySummary: createNodeStub(),
          characterDictionaryCurrent: createNodeStub(),
          characterDictionarySearchInput: searchInput,
          characterDictionarySearchButton: searchButton,
          characterDictionaryCandidates: candidates,
          characterDictionaryStatus: status,
        },
      } as never,
      {
        modalStateReader: { isAnyModalOpen: () => false },
        syncSettingsModalSubtitleSuppression: () => {},
      },
    );
    modal.wireDomEvents();

    await modal.openCharacterDictionaryModal();

    assert.deepEqual(searches, ['']);
    assert.equal(searchInput.value, 'Kage no Jitsuryokusha ni Naritakute!');
    assert.equal(candidates.children.length, 1);
    assert.match(status.textContent, /Enter a title/);

    searchInput.value = 'Eminence in Shadow';
    searchButton.dispatchEvent('click');
    await flushAsyncWork();

    assert.deepEqual(searches, ['', 'Eminence in Shadow']);
    assert.equal(candidates.children.length, 1);
    assert.match(status.textContent, /Select the correct AniList entry/);
  } finally {
    Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow });
    Object.defineProperty(globalThis, 'document', { configurable: true, value: previousDocument });
  }
});

test('character dictionary modal marks override candidate as selected', async () => {
  const previousWindow = globalThis.window;
  const previousDocument = globalThis.document;
  const snapshot: CharacterDictionarySelectionSnapshot = {
    seriesKey: 'konosuba-gods-blessing-on-this-wonderful-world-2016',
    guessTitle: "KonoSuba - God's blessing on this wonderful world!",
    current: null,
    override: {
      id: 21202,
      title: "KONOSUBA -God's blessing on this wonderful world!",
      episodes: 10,
    },
    candidates: [
      { id: 21202, title: "KONOSUBA -God's blessing on this wonderful world!", episodes: 10 },
    ],
  };
  const state = createRendererState();
  const candidates = createNodeStub();

  Object.defineProperty(globalThis, 'window', {
    configurable: true,
    value: {
      electronAPI: {
        getCharacterDictionarySelection: async () => snapshot,
        setCharacterDictionarySelection: async () => ({
          ok: true,
          seriesKey: snapshot.seriesKey,
          selected: snapshot.candidates[0]!,
          staleMediaIds: [],
        }),
        notifyOverlayModalClosed: () => {},
        notifyOverlayModalOpened: () => {},
      } satisfies Pick<
        ElectronAPI,
        | 'getCharacterDictionarySelection'
        | 'setCharacterDictionarySelection'
        | 'notifyOverlayModalClosed'
        | 'notifyOverlayModalOpened'
      >,
    },
  });
  Object.defineProperty(globalThis, 'document', {
    configurable: true,
    value: {
      createElement: () => createElementStub(),
    },
  });

  try {
    const modal = createCharacterDictionaryModal(
      {
        state,
        dom: {
          overlay: createNodeStub(),
          characterDictionaryModal: createNodeStub(true),
          characterDictionaryClose: createNodeStub(),
          characterDictionarySummary: createNodeStub(),
          characterDictionaryCurrent: createNodeStub(),
          characterDictionarySearchInput: createNodeStub(),
          characterDictionarySearchButton: createNodeStub(),
          characterDictionaryCandidates: candidates,
          characterDictionaryStatus: createNodeStub(),
        },
      } as never,
      {
        modalStateReader: { isAnyModalOpen: () => false },
        syncSettingsModalSubtitleSuppression: () => {},
      },
    );

    await modal.openCharacterDictionaryModal();

    const item = candidates.children[0] as { children: unknown[] };
    const button = item.children[1] as { textContent: string; disabled: boolean };
    assert.equal(button.textContent, 'Selected');
    assert.equal(button.disabled, true);
  } finally {
    Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow });
    Object.defineProperty(globalThis, 'document', { configurable: true, value: previousDocument });
  }
});

test('character dictionary modal does not resave the active override from keyboard apply', async () => {
  const previousWindow = globalThis.window;
  const snapshot: CharacterDictionarySelectionSnapshot = {
    seriesKey: 're-zero-starting-life-in-another-world-2016',
    guessTitle: 'Re ZERO, Starting Life in Another World',
    current: { id: 21355, title: 'Re:ZERO -Starting Life in Another World-', episodes: 25 },
    override: { id: 21355, title: 'Re:ZERO -Starting Life in Another World-', episodes: 25 },
    candidates: [{ id: 21355, title: 'Re:ZERO -Starting Life in Another World-', episodes: 25 }],
  };
  const calls: number[] = [];
  const state = createRendererState();

  Object.defineProperty(globalThis, 'window', {
    configurable: true,
    value: {
      electronAPI: {
        getCharacterDictionarySelection: async () => snapshot,
        setCharacterDictionarySelection: async (mediaId: number) => {
          calls.push(mediaId);
          return {
            ok: true,
            seriesKey: snapshot.seriesKey,
            selected: snapshot.candidates[0]!,
            staleMediaIds: [],
          };
        },
        notifyOverlayModalClosed: () => {},
        notifyOverlayModalOpened: () => {},
      } satisfies Pick<
        ElectronAPI,
        | 'getCharacterDictionarySelection'
        | 'setCharacterDictionarySelection'
        | 'notifyOverlayModalClosed'
        | 'notifyOverlayModalOpened'
      >,
    },
  });

  try {
    const modal = createCharacterDictionaryModal(
      {
        state,
        dom: {
          overlay: createNodeStub(),
          characterDictionaryModal: createNodeStub(true),
          characterDictionaryClose: createNodeStub(),
          characterDictionarySummary: createNodeStub(),
          characterDictionaryCurrent: createNodeStub(),
          characterDictionarySearchInput: createNodeStub(),
          characterDictionarySearchButton: createNodeStub(),
          characterDictionaryCandidates: createNodeStub(),
          characterDictionaryStatus: createNodeStub(),
        },
      } as never,
      {
        modalStateReader: { isAnyModalOpen: () => false },
        syncSettingsModalSubtitleSuppression: () => {},
      },
    );

    await modal.openCharacterDictionaryModal();
    modal.handleCharacterDictionaryKeydown({
      key: 'Enter',
      preventDefault: () => {},
    } as KeyboardEvent);
    await flushAsyncWork();

    assert.deepEqual(calls, []);
  } finally {
    Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow });
  }
});
