import { type } from '@tauri-apps/plugin-os';
import { debounce } from '@yaakapp-internal/lib';
import { settingsAtom } from '@yaakapp-internal/models';
import { atom, useAtomValue } from 'jotai';
import { useEffect } from 'react';
import { capitalize } from '../lib/capitalize';
import { jotaiStore } from '../lib/jotai';

const HOLD_KEYS = ['Shift', 'Control', 'Command', 'Alt', 'Meta'];
const SINGLE_WHITELIST = ['Delete', 'Enter', 'Backspace'];

export type HotkeyAction =
  | 'app.zoom_in'
  | 'app.zoom_out'
  | 'app.zoom_reset'
  | 'command_palette.toggle'
  | 'editor.autocomplete'
  | 'environment_editor.toggle'
  | 'hotkeys.showHelp'
  | 'model.create'
  | 'model.duplicate'
  | 'request.send'
  | 'request.rename'
  | 'switcher.next'
  | 'switcher.prev'
  | 'switcher.toggle'
  | 'settings.show'
  | 'sidebar.filter'
  | 'sidebar.selected.delete'
  | 'sidebar.selected.duplicate'
  | 'sidebar.selected.move'
  | 'sidebar.selected.rename'
  | 'sidebar.expand_all'
  | 'sidebar.collapse_all'
  | 'sidebar.focus'
  | 'sidebar.context_menu'
  | 'url_bar.focus'
  | 'workspace_settings.show';

/** Default hotkeys for macOS (uses Meta for Cmd) */
const defaultHotkeysMac: Record<HotkeyAction, string[]> = {
  'app.zoom_in': ['Meta+Equal'],
  'app.zoom_out': ['Meta+Minus'],
  'app.zoom_reset': ['Meta+0'],
  'command_palette.toggle': ['Meta+k'],
  'editor.autocomplete': ['Control+Space'],
  'environment_editor.toggle': ['Meta+Shift+e'],
  'request.rename': ['Control+Shift+r'],
  'request.send': ['Meta+Enter', 'Meta+r'],
  'hotkeys.showHelp': ['Meta+Shift+/'],
  'model.create': ['Meta+n'],
  'model.duplicate': ['Meta+d'],
  'switcher.next': ['Control+Shift+Tab'],
  'switcher.prev': ['Control+Tab'],
  'switcher.toggle': ['Meta+p'],
  'settings.show': ['Meta+,'],
  'sidebar.filter': ['Meta+f'],
  'sidebar.expand_all': ['Meta+Shift+Equal'],
  'sidebar.collapse_all': ['Meta+Shift+Minus'],
  'sidebar.selected.delete': ['Delete', 'Meta+Backspace'],
  'sidebar.selected.duplicate': ['Meta+d'],
  'sidebar.selected.move': [],
  'sidebar.selected.rename': ['Enter'],
  'sidebar.focus': ['Meta+b'],
  'sidebar.context_menu': ['Control+Enter'],
  'url_bar.focus': ['Meta+l'],
  'workspace_settings.show': ['Meta+;'],
};

/** Default hotkeys for Windows/Linux (uses Control for Ctrl) */
const defaultHotkeysOther: Record<HotkeyAction, string[]> = {
  'app.zoom_in': ['Control+Equal'],
  'app.zoom_out': ['Control+Minus'],
  'app.zoom_reset': ['Control+0'],
  'command_palette.toggle': ['Control+k'],
  'editor.autocomplete': ['Control+Space'],
  'environment_editor.toggle': ['Control+Shift+e'],
  'request.rename': ['F2'],
  'request.send': ['Control+Enter', 'Control+r'],
  'hotkeys.showHelp': ['Control+Shift+/'],
  'model.create': ['Control+n'],
  'model.duplicate': ['Control+d'],
  'switcher.next': ['Control+Shift+Tab'],
  'switcher.prev': ['Control+Tab'],
  'switcher.toggle': ['Control+p'],
  'settings.show': ['Control+,'],
  'sidebar.filter': ['Control+f'],
  'sidebar.expand_all': ['Control+Shift+Equal'],
  'sidebar.collapse_all': ['Control+Shift+Minus'],
  'sidebar.selected.delete': ['Delete', 'Control+Backspace'],
  'sidebar.selected.duplicate': ['Control+d'],
  'sidebar.selected.move': [],
  'sidebar.selected.rename': ['Enter'],
  'sidebar.focus': ['Control+b'],
  'sidebar.context_menu': ['Alt+Insert'],
  'url_bar.focus': ['Control+l'],
  'workspace_settings.show': ['Control+;'],
};

/** Get the default hotkeys for the current platform */
export const defaultHotkeys: Record<HotkeyAction, string[]> =
  type() === 'macos' ? defaultHotkeysMac : defaultHotkeysOther;

/** Atom that provides the effective hotkeys by merging defaults with user settings */
export const hotkeysAtom = atom((get) => {
  const settings = get(settingsAtom);
  const customHotkeys = settings?.hotkeys ?? {};

  // Merge default hotkeys with custom hotkeys from settings
  // Custom hotkeys override defaults for the same action
  // An empty array means the hotkey is intentionally disabled
  const merged: Record<HotkeyAction, string[]> = { ...defaultHotkeys };
  for (const [action, keys] of Object.entries(customHotkeys)) {
    if (action in defaultHotkeys && Array.isArray(keys)) {
      merged[action as HotkeyAction] = keys;
    }
  }
  return merged;
});

/** Helper function to get current hotkeys from the store */
function getHotkeys(): Record<HotkeyAction, string[]> {
  return jotaiStore.get(hotkeysAtom);
}

const hotkeyLabels: Record<HotkeyAction, string> = {
  'app.zoom_in': 'Zoom In',
  'app.zoom_out': 'Zoom Out',
  'app.zoom_reset': 'Zoom to Actual Size',
  'command_palette.toggle': 'Toggle Command Palette',
  'editor.autocomplete': 'Trigger Autocomplete',
  'environment_editor.toggle': 'Edit Environments',
  'hotkeys.showHelp': 'Show Keyboard Shortcuts',
  'model.create': 'New Request',
  'model.duplicate': 'Duplicate Request',
  'request.rename': 'Rename Active Request',
  'request.send': 'Send Active Request',
  'switcher.next': 'Go To Previous Request',
  'switcher.prev': 'Go To Next Request',
  'switcher.toggle': 'Toggle Request Switcher',
  'settings.show': 'Open Settings',
  'sidebar.filter': 'Filter Sidebar',
  'sidebar.expand_all': 'Expand All Folders',
  'sidebar.collapse_all': 'Collapse All Folders',
  'sidebar.selected.delete': 'Delete Selected Sidebar Item',
  'sidebar.selected.duplicate': 'Duplicate Selected Sidebar Item',
  'sidebar.selected.move': 'Move Selected to Workspace',
  'sidebar.selected.rename': 'Rename Selected Sidebar Item',
  'sidebar.focus': 'Focus or Toggle Sidebar',
  'sidebar.context_menu': 'Show Context Menu',
  'url_bar.focus': 'Focus URL',
  'workspace_settings.show': 'Open Workspace Settings',
};

const layoutInsensitiveKeys = [
  'Equal',
  'Minus',
  'BracketLeft',
  'BracketRight',
  'Backquote',
  'Space',
];

export const hotkeyActions: HotkeyAction[] = (
  Object.keys(defaultHotkeys) as (keyof typeof defaultHotkeys)[]
).sort((a, b) => {
  const scopeA = a.split('.')[0] || '';
  const scopeB = b.split('.')[0] || '';
  if (scopeA !== scopeB) {
    return scopeA.localeCompare(scopeB);
  }
  return hotkeyLabels[a].localeCompare(hotkeyLabels[b]);
});

export type HotKeyOptions = {
  enable?: boolean | (() => boolean);
  priority?: number;
  allowDefault?: boolean;
};

interface Callback {
  action: HotkeyAction;
  callback: (e: KeyboardEvent) => void;
  options: HotKeyOptions;
}

const callbacksAtom = atom<Callback[]>([]);
const currentKeysAtom = atom<Set<string>>(new Set([]));
export const sortedCallbacksAtom = atom((get) =>
  [...get(callbacksAtom)].sort((a, b) => (b.options.priority ?? 0) - (a.options.priority ?? 0)),
);

const clearCurrentKeysDebounced = debounce(() => {
  jotaiStore.set(currentKeysAtom, new Set([]));
}, 5000);

export function useHotKey(
  action: HotkeyAction | null,
  callback: (e: KeyboardEvent) => void,
  options: HotKeyOptions = {},
) {
  useEffect(() => {
    if (action == null) return;
    jotaiStore.set(callbacksAtom, (prev) => {
      const without = prev.filter((cb) => {
        const isTheSame = cb.action === action && cb.options.priority === options.priority;
        return !isTheSame;
      });
      const newCb: Callback = { action, callback, options };
      return [...without, newCb];
    });
    return () => {
      jotaiStore.set(callbacksAtom, (prev) => prev.filter((cb) => cb.callback !== callback));
    };
  }, [action, callback, options]);
}

export function useSubscribeHotKeys() {
  useEffect(() => {
    document.addEventListener('keyup', handleKeyUp, { capture: true });
    document.addEventListener('keydown', handleKeyDown, { capture: true });
    return () => {
      document.removeEventListener('keydown', handleKeyDown, { capture: true });
      document.removeEventListener('keyup', handleKeyUp, { capture: true });
    };
  }, []);
}

function handleKeyUp(e: KeyboardEvent) {
  const keyToRemove = layoutInsensitiveKeys.includes(e.code) ? e.code : e.key;
  const currentKeys = new Set(jotaiStore.get(currentKeysAtom));
  currentKeys.delete(keyToRemove);

  // Clear all keys if no longer holding modifier
  // HACK: This is to get around the case of DOWN SHIFT -> DOWN : -> UP SHIFT -> UP ;
  //  As you see, the ":" is not removed because it turned into ";" when shift was released
  const isHoldingModifier = e.altKey || e.ctrlKey || e.metaKey || e.shiftKey;
  if (!isHoldingModifier) {
    currentKeys.clear();
  }

  jotaiStore.set(currentKeysAtom, currentKeys);
}

function handleKeyDown(e: KeyboardEvent) {
  // Don't add key if not holding modifier
  const isValidKeymapKey =
    e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || SINGLE_WHITELIST.includes(e.key);
  if (!isValidKeymapKey) {
    return;
  }

  // Don't add hold keys
  if (HOLD_KEYS.includes(e.key)) {
    return;
  }

  const keyToAdd = layoutInsensitiveKeys.includes(e.code) ? e.code : e.key;
  const currentKeys = new Set(jotaiStore.get(currentKeysAtom));
  currentKeys.add(keyToAdd);

  const currentKeysWithModifiers = new Set(currentKeys);
  if (e.altKey) currentKeysWithModifiers.add('Alt');
  if (e.ctrlKey) currentKeysWithModifiers.add('Control');
  if (e.metaKey) currentKeysWithModifiers.add('Meta');
  if (e.shiftKey) currentKeysWithModifiers.add('Shift');

  // Don't trigger if the user is focused within an element that explicitly disableds hotkeys
  if (document.activeElement?.closest('[data-disable-hotkey]')) {
    return;
  }

  // Don't support certain single-key combinations within inputs
  if (
    (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) &&
    currentKeysWithModifiers.size === 1 &&
    (currentKeysWithModifiers.has('Backspace') || currentKeysWithModifiers.has('Delete'))
  ) {
    return;
  }

  const executed: string[] = [];
  for (const { action, callback, options } of jotaiStore.get(sortedCallbacksAtom)) {
    const enable = typeof options.enable === 'function' ? options.enable() : options.enable;
    if (enable === false) {
      continue;
    }

    if (keysMatchAction(Array.from(currentKeysWithModifiers), action)) {
      if (!options.allowDefault) {
        e.preventDefault();
        e.stopPropagation();
      }
      callback(e);
      executed.push(`${action} ${options.priority ?? 0}`);
      break;
    }
  }

  if (executed.length > 0) {
    console.log('Executed hotkey', executed.join(', '));
    jotaiStore.set(currentKeysAtom, new Set([]));
  }
  clearCurrentKeysDebounced();
}

export function useHotkeyLabel(action: HotkeyAction): string {
  return hotkeyLabels[action];
}

export function getHotkeyScope(action: HotkeyAction): string {
  const scope = action.split('.')[0];
  return scope || '';
}

export function formatHotkeyString(trigger: string): string[] {
  const os = type();
  const parts = trigger.split('+');
  const labelParts: string[] = [];

  for (const p of parts) {
    if (os === 'macos') {
      if (p === 'Meta') {
        labelParts.push('⌘');
      } else if (p === 'Shift') {
        labelParts.push('⇧');
      } else if (p === 'Control') {
        labelParts.push('⌃');
      } else if (p === 'Alt') {
        labelParts.push('⌥');
      } else if (p === 'Enter') {
        labelParts.push('↩');
      } else if (p === 'Tab') {
        labelParts.push('⇥');
      } else if (p === 'Backspace') {
        labelParts.push('⌫');
      } else if (p === 'Delete') {
        labelParts.push('⌦');
      } else if (p === 'Minus') {
        labelParts.push('-');
      } else if (p === 'Plus') {
        labelParts.push('+');
      } else if (p === 'Equal') {
        labelParts.push('=');
      } else if (p === 'Space') {
        labelParts.push('Space');
      } else {
        labelParts.push(capitalize(p));
      }
    } else {
      if (p === 'Control') {
        labelParts.push('Ctrl');
      } else if (p === 'Space') {
        labelParts.push('Space');
      } else {
        labelParts.push(capitalize(p));
      }
    }
  }

  if (os === 'macos') {
    return labelParts;
  }
  return [labelParts.join('+')];
}

export function useFormattedHotkey(action: HotkeyAction | null): string[] | null {
  const hotkeys = useAtomValue(hotkeysAtom);
  const trigger = action != null ? (hotkeys[action]?.[0] ?? null) : null;
  if (trigger == null) {
    return null;
  }

  return formatHotkeyString(trigger);
}

function compareKeys(keysA: string[], keysB: string[]) {
  if (keysA.length !== keysB.length) return false;
  const sortedA = keysA
    .map((k) => k.toLowerCase())
    .sort()
    .join('::');
  const sortedB = keysB
    .map((k) => k.toLowerCase())
    .sort()
    .join('::');
  return sortedA === sortedB;
}

/** Build the full key combination from a KeyboardEvent including modifiers */
function getKeysFromEvent(e: KeyboardEvent): string[] {
  const keys: string[] = [];
  if (e.altKey) keys.push('Alt');
  if (e.ctrlKey) keys.push('Control');
  if (e.metaKey) keys.push('Meta');
  if (e.shiftKey) keys.push('Shift');

  // Add the actual key (use code for layout-insensitive keys)
  const keyToAdd = layoutInsensitiveKeys.includes(e.code) ? e.code : e.key;
  keys.push(keyToAdd);

  return keys;
}

/** Check if a set of pressed keys matches any hotkey for the given action */
function keysMatchAction(keys: string[], action: HotkeyAction): boolean {
  const hotkeys = getHotkeys();
  const hkKeys = hotkeys[action];
  if (!hkKeys || hkKeys.length === 0) return false;

  for (const hkKey of hkKeys) {
    const hotkeyParts = hkKey.split('+');
    if (compareKeys(hotkeyParts, keys)) {
      return true;
    }
  }
  return false;
}

/** Check if a KeyboardEvent matches a hotkey action */
export function eventMatchesHotkey(e: KeyboardEvent, action: HotkeyAction): boolean {
  const keys = getKeysFromEvent(e);
  return keysMatchAction(keys, action);
}
