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

import {
  createManagedLocalSubtitleSelectionRuntime,
  resolveManagedLocalSubtitleSelection,
} from './local-subtitle-selection';

const mixedLanguageTrackList = [
  { type: 'sub', id: 1, lang: 'pt', title: '[Infinite]', external: false, selected: true },
  { type: 'sub', id: 2, lang: 'pt', title: '[Moshi Moshi]', external: false },
  { type: 'sub', id: 3, lang: 'en', title: '(Vivid)', external: false },
  { type: 'sub', id: 9, lang: 'en', title: 'English(US)', external: false },
  { type: 'sub', id: 11, lang: 'en', title: 'en.srt', external: true },
  { type: 'sub', id: 12, lang: 'ja', title: 'ja.srt', external: true },
];

const unlabeledExternalSidecarTrackList = [
  { type: 'sub', id: 1, lang: 'eng', title: 'English ASS', external: false, selected: true },
  { type: 'sub', id: 2, title: 'srt', external: true },
];

test('resolveManagedLocalSubtitleSelection prefers default Japanese primary and English secondary tracks', () => {
  const result = resolveManagedLocalSubtitleSelection({
    trackList: mixedLanguageTrackList,
    primaryLanguages: [],
    secondaryLanguages: [],
  });

  assert.equal(result.primaryTrackId, 12);
  assert.equal(result.secondaryTrackId, 11);
});

test('resolveManagedLocalSubtitleSelection respects configured language overrides', () => {
  const result = resolveManagedLocalSubtitleSelection({
    trackList: mixedLanguageTrackList,
    primaryLanguages: ['pt'],
    secondaryLanguages: ['ja'],
  });

  assert.equal(result.primaryTrackId, 1);
  assert.equal(result.secondaryTrackId, 12);
});

test('resolveManagedLocalSubtitleSelection promotes a single unlabeled external sidecar to primary', () => {
  const result = resolveManagedLocalSubtitleSelection({
    trackList: unlabeledExternalSidecarTrackList,
    primaryLanguages: [],
    secondaryLanguages: [],
  });

  assert.equal(result.primaryTrackId, 2);
  assert.equal(result.secondaryTrackId, 1);
});

test('resolveManagedLocalSubtitleSelection does not guess between multiple unlabeled external sidecars', () => {
  const result = resolveManagedLocalSubtitleSelection({
    trackList: [
      ...unlabeledExternalSidecarTrackList,
      { type: 'sub', id: 3, title: 'subrip', external: true },
    ],
    primaryLanguages: [],
    secondaryLanguages: [],
  });

  assert.equal(result.primaryTrackId, null);
  assert.equal(result.secondaryTrackId, 1);
});

test('managed local subtitle selection runtime applies preferred tracks once for a local media path', async () => {
  const commands: Array<Array<string | number>> = [];
  const scheduled: Array<() => void> = [];

  const runtime = createManagedLocalSubtitleSelectionRuntime({
    getCurrentMediaPath: () => '/videos/example.mkv',
    getMpvClient: () =>
      ({
        connected: true,
        requestProperty: async (name: string) => {
          if (name === 'track-list') {
            return mixedLanguageTrackList;
          }
          throw new Error(`Unexpected property: ${name}`);
        },
      }) as never,
    getPrimarySubtitleLanguages: () => [],
    getSecondarySubtitleLanguages: () => [],
    sendMpvCommand: (command) => {
      commands.push(command);
    },
    schedule: (callback) => {
      scheduled.push(callback);
      return 1 as never;
    },
    clearScheduled: () => {},
  });

  runtime.handleMediaPathChange('/videos/example.mkv');
  scheduled.shift()?.();
  await new Promise((resolve) => setTimeout(resolve, 0));
  runtime.handleSubtitleTrackListChange(mixedLanguageTrackList);

  assert.deepEqual(commands, [
    ['set_property', 'sid', 12],
    ['set_property', 'secondary-sid', 11],
  ]);
});

test('managed local subtitle selection runtime promotes a single unlabeled external sidecar over embedded english', async () => {
  const commands: Array<Array<string | number>> = [];
  const scheduled: Array<() => void> = [];

  const runtime = createManagedLocalSubtitleSelectionRuntime({
    getCurrentMediaPath: () => '/videos/example.mkv',
    getMpvClient: () =>
      ({
        connected: true,
        requestProperty: async (name: string) => {
          if (name === 'track-list') {
            return unlabeledExternalSidecarTrackList;
          }
          throw new Error(`Unexpected property: ${name}`);
        },
      }) as never,
    getPrimarySubtitleLanguages: () => [],
    getSecondarySubtitleLanguages: () => [],
    sendMpvCommand: (command) => {
      commands.push(command);
    },
    schedule: (callback) => {
      scheduled.push(callback);
      return 1 as never;
    },
    clearScheduled: () => {},
  });

  runtime.handleMediaPathChange('/videos/example.mkv');
  scheduled.shift()?.();
  await new Promise((resolve) => setTimeout(resolve, 0));
  runtime.handleSubtitleTrackListChange(unlabeledExternalSidecarTrackList);

  assert.deepEqual(commands, [
    ['set_property', 'sid', 2],
    ['set_property', 'secondary-sid', 1],
  ]);
});

test('managed local subtitle selection keeps waiting for primary after early secondary-only track list', () => {
  const commands: Array<Array<string | number>> = [];
  const runtime = createManagedLocalSubtitleSelectionRuntime({
    getCurrentMediaPath: () => '/videos/example.mkv',
    getMpvClient: () => null,
    getPrimarySubtitleLanguages: () => [],
    getSecondarySubtitleLanguages: () => [],
    sendMpvCommand: (command) => {
      commands.push(command);
    },
    schedule: () => 1 as never,
    clearScheduled: () => {},
  });

  runtime.handleMediaPathChange('/videos/example.mkv');
  runtime.handleSubtitleTrackListChange([
    { type: 'sub', id: 1, lang: 'eng', title: 'ASS', external: false },
    { type: 'sub', id: 2, lang: 'en', title: 'en.srt', external: true },
  ]);
  runtime.handleSubtitleTrackListChange([
    { type: 'sub', id: 1, lang: 'eng', title: 'ASS', external: false },
    { type: 'sub', id: 2, lang: 'en', title: 'en.srt', external: true },
    { type: 'sub', id: 3, lang: 'ja', title: 'ja.srt', external: true },
  ]);

  assert.deepEqual(commands, [
    ['set_property', 'secondary-sid', 2],
    ['set_property', 'sid', 3],
  ]);
});

test('managed local subtitle selection keeps pending refresh after early primary-only track list', async () => {
  const commands: Array<Array<string | number>> = [];
  const scheduled = new Map<number, () => void>();
  let nextTimerId = 1;

  const runtime = createManagedLocalSubtitleSelectionRuntime({
    getCurrentMediaPath: () => '/videos/example.mkv',
    getMpvClient: () =>
      ({
        connected: true,
        requestProperty: async (name: string) => {
          if (name === 'track-list') {
            return [
              { type: 'sub', id: 3, lang: 'ja', title: 'ja.srt', external: true },
              { type: 'sub', id: 4, lang: 'en', title: 'en.srt', external: true },
            ];
          }
          throw new Error(`Unexpected property: ${name}`);
        },
      }) as never,
    getPrimarySubtitleLanguages: () => [],
    getSecondarySubtitleLanguages: () => [],
    sendMpvCommand: (command) => {
      commands.push(command);
    },
    schedule: (callback) => {
      const timerId = nextTimerId++;
      scheduled.set(timerId, callback);
      return timerId as never;
    },
    clearScheduled: (timer) => {
      scheduled.delete(timer as never);
    },
  });

  runtime.handleMediaPathChange('/videos/example.mkv');
  runtime.handleSubtitleTrackListChange([
    { type: 'sub', id: 3, lang: 'ja', title: 'ja.srt', external: true },
  ]);

  assert.deepEqual(commands, [['set_property', 'sid', 3]]);
  assert.equal(scheduled.size, 1);

  const refresh = [...scheduled.values()][0];
  assert.ok(refresh);
  refresh();
  await new Promise((resolve) => setTimeout(resolve, 0));

  assert.deepEqual(commands, [
    ['set_property', 'sid', 3],
    ['set_property', 'secondary-sid', 4],
  ]);
});
