// Toggle-style voice hotkeys (mute / deafen). Distinct from PTT (hold-style
// single-key). Keeps the Discord muscle-memory defaults — Ctrl+Shift+M for
// mute, Ctrl+Shift+D for deafen — but ships disabled so they never collide
// with something else on first run.
//
// Chord-capable: each binding stores a base key (KeyboardEvent.code) plus
// modifier flags. The keyLabel field is pre-rendered so UI and in-call hints
// don't have to derive it on every render.

import { keyCodeToLabel } from './pttSettings';

const STORAGE_KEY = 'chatapp.voiceHotkeys.v1';

export interface VoiceHotkeyBinding {
  /** KeyboardEvent.code of the base key. */
  key: string;
  /** Pre-rendered label. Includes modifier prefixes, e.g. "Ctrl+Shift+M". */
  keyLabel: string;
  ctrl: boolean;
  shift: boolean;
  alt: boolean;
  enabled: boolean;
}

export interface VoiceHotkeys {
  mute: VoiceHotkeyBinding;
  deafen: VoiceHotkeyBinding;
  /** Hang up the active call. Discord uses no default — easy to mis-fire. */
  hangup: VoiceHotkeyBinding;
  /** Toggle outgoing screen share. */
  screenShare: VoiceHotkeyBinding;
  /** Toggle outgoing camera. */
  video: VoiceHotkeyBinding;
}

export type VoiceHotkeyKind = keyof VoiceHotkeys;

const DEFAULTS: VoiceHotkeys = {
  mute: {
    key: 'KeyM',
    keyLabel: 'Ctrl+Shift+M',
    ctrl: true,
    shift: true,
    alt: false,
    enabled: false,
  },
  deafen: {
    key: 'KeyD',
    keyLabel: 'Ctrl+Shift+D',
    ctrl: true,
    shift: true,
    alt: false,
    enabled: false,
  },
  hangup: {
    key: 'KeyH',
    keyLabel: 'Ctrl+Shift+H',
    ctrl: true,
    shift: true,
    alt: false,
    enabled: false,
  },
  screenShare: {
    key: 'KeyE',
    keyLabel: 'Ctrl+Shift+E',
    ctrl: true,
    shift: true,
    alt: false,
    enabled: false,
  },
  video: {
    key: 'KeyV',
    keyLabel: 'Ctrl+Shift+V',
    ctrl: true,
    shift: true,
    alt: false,
    enabled: false,
  },
};

type Listener = (s: VoiceHotkeys) => void;
const listeners = new Set<Listener>();

let cached: VoiceHotkeys | null = null;

function validateBinding(raw: unknown, fallback: VoiceHotkeyBinding): VoiceHotkeyBinding {
  if (!raw || typeof raw !== 'object') return fallback;
  const b = raw as Partial<VoiceHotkeyBinding>;
  return {
    key: typeof b.key === 'string' && b.key ? b.key : fallback.key,
    keyLabel: typeof b.keyLabel === 'string' && b.keyLabel ? b.keyLabel : fallback.keyLabel,
    ctrl: typeof b.ctrl === 'boolean' ? b.ctrl : fallback.ctrl,
    shift: typeof b.shift === 'boolean' ? b.shift : fallback.shift,
    alt: typeof b.alt === 'boolean' ? b.alt : fallback.alt,
    enabled: typeof b.enabled === 'boolean' ? b.enabled : fallback.enabled,
  };
}

function read(): VoiceHotkeys {
  if (cached) return cached;
  try {
    const raw = window.localStorage.getItem(STORAGE_KEY);
    if (!raw) {
      cached = DEFAULTS;
      return cached;
    }
    const parsed = JSON.parse(raw) as Partial<VoiceHotkeys>;
    cached = {
      mute: validateBinding(parsed.mute, DEFAULTS.mute),
      deafen: validateBinding(parsed.deafen, DEFAULTS.deafen),
      hangup: validateBinding(parsed.hangup, DEFAULTS.hangup),
      screenShare: validateBinding(parsed.screenShare, DEFAULTS.screenShare),
      video: validateBinding(parsed.video, DEFAULTS.video),
    };
    return cached;
  } catch {
    cached = DEFAULTS;
    return cached;
  }
}

function write(s: VoiceHotkeys): void {
  cached = s;
  try {
    window.localStorage.setItem(STORAGE_KEY, JSON.stringify(s));
  } catch {
    /* quota / private mode */
  }
  for (const l of listeners) l(s);
}

export function getVoiceHotkeys(): VoiceHotkeys {
  return read();
}

export function updateVoiceHotkey(
  kind: VoiceHotkeyKind,
  patch: Partial<VoiceHotkeyBinding>,
): VoiceHotkeys {
  const cur = read();
  const next: VoiceHotkeys = {
    ...cur,
    [kind]: {
      ...cur[kind],
      ...patch,
    },
  };
  // Keep the label in sync with the key + modifier flags so callers don't
  // have to remember to pass keyLabel too.
  next[kind].keyLabel = renderBindingLabel(next[kind]);
  write(next);
  return next;
}

export function subscribeVoiceHotkeys(listener: Listener): () => void {
  listeners.add(listener);
  return () => listeners.delete(listener);
}

// Derives "Ctrl+Shift+M" from a binding. The base key is normalised via
// keyCodeToLabel so "KeyM" → "M" etc, matching the PTT label rendering.
export function renderBindingLabel(b: VoiceHotkeyBinding): string {
  const parts: string[] = [];
  if (b.ctrl) parts.push('Ctrl');
  if (b.shift) parts.push('Shift');
  if (b.alt) parts.push('Alt');
  parts.push(keyCodeToLabel(b.key));
  return parts.join('+');
}

// Tauri global-shortcut accelerator string format. `CommandOrControl` maps
// to Cmd on macOS and Ctrl on Windows/Linux so the same binding works on
// every platform without platform-specific storage.
export function bindingToTauriShortcut(b: VoiceHotkeyBinding): string {
  const base = b.key.startsWith('Key')
    ? b.key.slice(3)
    : b.key.startsWith('Digit')
      ? b.key.slice(5)
      : b.key;
  const parts: string[] = [];
  if (b.ctrl) parts.push('CommandOrControl');
  if (b.shift) parts.push('Shift');
  if (b.alt) parts.push('Alt');
  parts.push(base);
  return parts.join('+');
}

// Check if a browser KeyboardEvent matches a binding exactly (including
// modifier state). Used by the window-level fallback listener when the
// Tauri global-shortcut registration isn't available.
export function eventMatchesBinding(b: VoiceHotkeyBinding, e: KeyboardEvent): boolean {
  if (!b.enabled) return false;
  if (e.code !== b.key) return false;
  if (e.ctrlKey !== b.ctrl && e.metaKey !== b.ctrl) return false;
  if (e.shiftKey !== b.shift) return false;
  if (e.altKey !== b.alt) return false;
  return true;
}
