import type { Keybinding } from '../types/runtime';

export type KeyInputMode = 'accelerator' | 'dom-code' | 'code' | 'mpv-key';

export interface KeyboardInputLike {
  code: string;
  key: string;
  ctrlKey: boolean;
  altKey: boolean;
  shiftKey: boolean;
  metaKey: boolean;
}

export interface MouseInputLike {
  button: number;
  ctrlKey: boolean;
  altKey: boolean;
  shiftKey: boolean;
  metaKey: boolean;
}

export interface MpvKeybindingRow {
  defaultKey: string;
  key: string;
  command: (string | number)[] | null;
  commandText: string;
  isDefault: boolean;
}

const MODIFIER_CODES = new Set([
  'AltLeft',
  'AltRight',
  'ControlLeft',
  'ControlRight',
  'MetaLeft',
  'MetaRight',
  'ShiftLeft',
  'ShiftRight',
]);

const ELECTRON_KEY_BY_CODE: Record<string, string> = {
  Backquote: 'Backquote',
  Backslash: 'Backslash',
  BracketLeft: 'BracketLeft',
  BracketRight: 'BracketRight',
  Comma: 'Comma',
  Delete: 'Delete',
  End: 'End',
  Enter: 'Enter',
  Equal: 'Plus',
  Escape: 'Escape',
  Home: 'Home',
  Insert: 'Insert',
  Minus: 'Minus',
  PageDown: 'PageDown',
  PageUp: 'PageUp',
  Period: 'Period',
  Quote: 'Quote',
  Semicolon: 'Semicolon',
  Slash: 'Slash',
  Space: 'Space',
  Tab: 'Tab',
};

const MPV_KEY_BY_CODE: Record<string, string> = {
  Backspace: 'BS',
  Backquote: '`',
  Backslash: '\\',
  BracketLeft: '[',
  BracketRight: ']',
  Comma: ',',
  Delete: 'DEL',
  End: 'END',
  Enter: 'ENTER',
  Equal: '=',
  Escape: 'ESC',
  Home: 'HOME',
  Insert: 'INS',
  Minus: '-',
  PageDown: 'PGDWN',
  PageUp: 'PGUP',
  Period: '.',
  Quote: "'",
  Semicolon: ';',
  Slash: '/',
  Space: 'SPACE',
  Tab: 'TAB',
};

const MPV_MOUSE_BUTTON_BY_BUTTON: Record<number, string> = {
  0: 'MBTN_LEFT',
  1: 'MBTN_MID',
  2: 'MBTN_RIGHT',
  3: 'MBTN_BACK',
  4: 'MBTN_FORWARD',
};

function commandEquals(a: Keybinding['command'], b: Keybinding['command']): boolean {
  return JSON.stringify(a) === JSON.stringify(b);
}

function normalizeUserBindings(userBindings: unknown): Keybinding[] {
  if (!Array.isArray(userBindings)) return [];
  return userBindings.filter((binding): binding is Keybinding => {
    if (!binding || typeof binding !== 'object') return false;
    const candidate = binding as Keybinding;
    return (
      typeof candidate.key === 'string' &&
      (candidate.command === null || Array.isArray(candidate.command))
    );
  });
}

function electronKeyToken(input: KeyboardInputLike): string | null {
  if (/^Key[A-Z]$/.test(input.code)) return input.code.slice(3);
  if (/^Digit[0-9]$/.test(input.code)) return input.code.slice(5);
  if (/^Numpad[0-9]$/.test(input.code)) return `num${input.code.slice(6)}`;
  if (/^F\d{1,2}$/.test(input.code)) return input.code;
  if (input.code.startsWith('Arrow')) return input.code.replace('Arrow', '');
  return ELECTRON_KEY_BY_CODE[input.code] ?? null;
}

function mpvKeyToken(input: KeyboardInputLike): string | null {
  if (/^Key[A-Z]$/.test(input.code)) {
    return input.key.length === 1 ? input.key : input.code.slice(3).toLowerCase();
  }
  if (/^Digit[0-9]$/.test(input.code)) return input.code.slice(5);
  if (/^Numpad[0-9]$/.test(input.code)) return `KP${input.code.slice(6)}`;
  if (/^F\d{1,2}$/.test(input.code)) return input.code;
  if (input.code.startsWith('Arrow')) return input.code.replace('Arrow', '').toUpperCase();
  return MPV_KEY_BY_CODE[input.code] ?? null;
}

export function keyboardEventToConfigKey(
  input: KeyboardInputLike,
  mode: KeyInputMode,
): string | null {
  if (!input.code || MODIFIER_CODES.has(input.code)) {
    return null;
  }

  if (mode === 'code') {
    return input.code;
  }

  const parts: string[] = [];
  if (mode === 'mpv-key') {
    if (input.ctrlKey) parts.push('Ctrl');
    if (input.altKey) parts.push('Alt');
    if (input.shiftKey) parts.push('Shift');
    if (input.metaKey) parts.push('Meta');
    const key = mpvKeyToken(input);
    return key ? [...parts, key].join('+') : null;
  }

  if (mode === 'accelerator') {
    if (input.ctrlKey || input.metaKey) parts.push('CommandOrControl');
    if (input.altKey) parts.push('Alt');
    if (input.shiftKey) parts.push('Shift');
    const key = electronKeyToken(input);
    return key ? [...parts, key].join('+') : null;
  }

  if (input.ctrlKey) parts.push('Ctrl');
  if (input.altKey) parts.push('Alt');
  if (input.shiftKey) parts.push('Shift');
  if (input.metaKey) parts.push('Meta');
  return [...parts, input.code].join('+');
}

export function mouseEventToConfigKey(input: MouseInputLike, mode: KeyInputMode): string | null {
  if (mode !== 'dom-code') {
    return null;
  }

  const key = MPV_MOUSE_BUTTON_BY_BUTTON[input.button];
  if (!key) {
    return null;
  }

  const parts: string[] = [];
  if (input.ctrlKey) parts.push('Ctrl');
  if (input.altKey) parts.push('Alt');
  if (input.shiftKey) parts.push('Shift');
  if (input.metaKey) parts.push('Meta');
  return [...parts, key].join('+');
}

export function createMpvKeybindingRows(
  defaultBindings: Keybinding[],
  userBindings: unknown,
): MpvKeybindingRow[] {
  const normalizedUserBindings = normalizeUserBindings(userBindings);
  const userByKey = new Map(normalizedUserBindings.map((binding) => [binding.key, binding]));
  const consumedUserKeys = new Set<string>();

  const rows = defaultBindings.map((binding) => {
    const override = userByKey.get(binding.key);
    if (override?.command === null) {
      const movedOverride = normalizedUserBindings.find(
        (candidate) =>
          candidate.key !== binding.key && commandEquals(candidate.command, binding.command),
      );
      if (movedOverride) {
        consumedUserKeys.add(binding.key);
        consumedUserKeys.add(movedOverride.key);
        return {
          defaultKey: binding.key,
          key: movedOverride.key,
          command: movedOverride.command,
          commandText: JSON.stringify(movedOverride.command ?? null),
          isDefault: true,
        };
      }
    }

    if (override) {
      consumedUserKeys.add(binding.key);
    }
    const command = override?.command ?? binding.command;
    return {
      defaultKey: binding.key,
      key: binding.key,
      command,
      commandText: JSON.stringify(command ?? null),
      isDefault: true,
    };
  });

  for (const binding of normalizedUserBindings) {
    if (consumedUserKeys.has(binding.key)) {
      continue;
    }
    if (defaultBindings.some((defaultBinding) => defaultBinding.key === binding.key)) {
      continue;
    }
    rows.push({
      defaultKey: binding.key,
      key: binding.key,
      command: binding.command,
      commandText: JSON.stringify(binding.command ?? null),
      isDefault: false,
    });
  }

  return rows;
}

export function parseMpvCommandText(value: string): Keybinding['command'] | undefined {
  try {
    const parsed = JSON.parse(value);
    if (parsed === null) return null;
    if (
      Array.isArray(parsed) &&
      parsed.every((entry) => typeof entry === 'string' || typeof entry === 'number')
    ) {
      return parsed;
    }
  } catch {
    return undefined;
  }
  return undefined;
}

export function buildMpvKeybindingConfigValue(
  defaultBindings: Keybinding[],
  rows: MpvKeybindingRow[],
): Keybinding[] {
  const next: Keybinding[] = [];

  for (const defaultBinding of defaultBindings) {
    const row = rows.find((candidate) => candidate.defaultKey === defaultBinding.key);
    if (!row) {
      next.push({ key: defaultBinding.key, command: null });
      continue;
    }

    if (row.key !== defaultBinding.key) {
      next.push({ key: defaultBinding.key, command: null });
      if (row.command !== null) {
        next.push({ key: row.key, command: row.command });
      }
      continue;
    }

    if (!commandEquals(row.command, defaultBinding.command)) {
      next.push({ key: row.key, command: row.command });
    }
  }

  for (const row of rows.filter((candidate) => !candidate.isDefault)) {
    next.push({ key: row.key, command: row.command });
  }

  return next;
}
