/**
 * Per-OS hotkey capability matrix.
 *
 * Different global-hotkey backends refuse different combos. Rather
 * than let the user save a combo the OS will silently ignore, we
 * validate before save and surface a clear "why" string.
 *
 * Covered failures (brief item #5):
 *   - X11: single-key combos (no modifier) get swallowed by the user's
 *     editor / IRC client / whatever; they are useless as globals.
 *     Handy #917 / #1019.
 *   - Windows: right-hand modifiers (RCtrl, RAlt) don't always register
 *     through RegisterHotKey; the user gets a dead shortcut. Handy
 *     #966. We reject a combo whose only modifier is a right-hand one
 *     — if the user binds Ctrl+R we don't care whether left or right
 *     Ctrl fired it.
 *   - macOS: Fn-only combos require an NSEvent private API that
 *     RegisterHotKey-equivalents don't expose (Whispering #549 area).
 *   - Wayland (Linux): single-key combos are rejected even more
 *     aggressively than X11 because compositors vary on whether they
 *     forward a bare key to a global listener at all.
 *
 * All rules are pure — the UI calls `validateHotkey(combo, os)` and
 * gates save on `result.valid`. No backend round-trip required.
 */

export type HotkeyOs = "windows" | "macos" | "linux" | "unknown";

export interface HotkeyValidity {
  /** Whether this combo is safe to save. */
  valid: boolean;
  /**
   * User-visible "why not" string when `valid` is false. Null when
   * the combo is OK. Copy is written for inline display — no leading
   * capitalisation assumption, no trailing period needed.
   */
  reason: string | null;
}

export interface HotkeyParts {
  modifiers: string[]; // e.g. ["Ctrl", "Shift"]
  trigger: string;     // e.g. "R" — may be empty if we're mid-record
}

/** Tokenise a `+`-joined hotkey the way the lumotia-hotkey parser does,
 *  but without demanding a fully-valid combo (we validate next). The
 *  last part is always the trigger key; everything before is a
 *  modifier. An empty/whitespace combo returns empty parts. */
export function parseHotkey(combo: string): HotkeyParts {
  const trimmed = combo?.trim?.() ?? "";
  if (!trimmed) return { modifiers: [], trigger: "" };
  const tokens = trimmed.split("+").map((t) => t.trim()).filter(Boolean);
  if (tokens.length === 0) return { modifiers: [], trigger: "" };
  const trigger = tokens[tokens.length - 1];
  const modifiers = tokens.slice(0, -1);
  return { modifiers, trigger };
}

// Fn and function-key helpers.
const FUNCTION_KEYS = new Set([
  "F1", "F2", "F3", "F4", "F5", "F6",
  "F7", "F8", "F9", "F10", "F11", "F12",
  "F13", "F14", "F15", "F16", "F17", "F18", "F19", "F20",
  "F21", "F22", "F23", "F24",
]);

function normaliseModifier(mod: string): string {
  const m = mod.trim().toLowerCase();
  if (m === "cmd" || m === "meta" || m === "super" || m === "win") return "super";
  if (m === "option") return "alt";
  return m;
}

function isRightHandModifier(mod: string): boolean {
  const m = mod.trim().toLowerCase();
  return m === "rctrl" || m === "ralt" || m === "rshift" || m === "rsuper" || m === "rmeta";
}

function isFnModifier(mod: string): boolean {
  return mod.trim().toLowerCase() === "fn";
}

/** Validate a combo for the given OS. `unknown` OS passes unchanged
 *  — we'd rather ship a flawed combo than reject one we can't check. */
export function validateHotkey(combo: string, os: HotkeyOs): HotkeyValidity {
  const parts = parseHotkey(combo);
  if (!parts.trigger) {
    return {
      valid: false,
      reason: "pick a key — hotkeys need at least one non-modifier",
    };
  }

  const hasModifier = parts.modifiers.length > 0;
  const normalisedMods = parts.modifiers.map(normaliseModifier);

  if (os === "linux" || os === "unknown") {
    // X11 AND Wayland: single-key globals are a footgun everywhere on
    // Linux. Function keys F13+ are the conventional escape hatch
    // (they're not on anyone's keyboard, so they don't collide) —
    // allow them single-key.
    if (!hasModifier && !FUNCTION_KEYS.has(parts.trigger)) {
      return {
        valid: false,
        reason:
          "add Ctrl, Shift, Alt, or Super — single keys get eaten by whichever app is focused",
      };
    }
  }

  if (os === "windows") {
    // Right-hand-only modifiers break RegisterHotKey in some layouts.
    const onlyRightHand =
      parts.modifiers.length > 0
      && parts.modifiers.every(isRightHandModifier);
    if (onlyRightHand) {
      return {
        valid: false,
        reason:
          "right-hand-only modifiers (RCtrl / RAlt) don't always register as global shortcuts on Windows — use the left-hand equivalent",
      };
    }
    if (!hasModifier) {
      return {
        valid: false,
        reason: "Windows requires at least one modifier on a global hotkey",
      };
    }
  }

  if (os === "macos") {
    if (normalisedMods.some(isFnModifier) && parts.modifiers.length === 1) {
      return {
        valid: false,
        reason:
          "Fn-only combos aren't available to Tauri global shortcuts on macOS — pair Fn with Cmd/Ctrl/Option, or pick a different trigger",
      };
    }
    if (!hasModifier && !FUNCTION_KEYS.has(parts.trigger)) {
      return {
        valid: false,
        reason:
          "add Cmd, Ctrl, Option, or Shift — macOS requires a modifier on bare-key global shortcuts",
      };
    }
  }

  return { valid: true, reason: null };
}
