export const DEFINE_SHORTCUT = `import type { MaybeRef } from "vue";

type KbdKeysSpecificMap = {
  meta: string;
  alt: string;
  ctrl: string;
};

export const kbdKeysMap = {
  meta: "",
  ctrl: "",
  alt: "",
  win: "⊞",
  command: "⌘",
  shift: "⇧",
  control: "⌃",
  option: "⌥",
  enter: "↵",
  delete: "⌦",
  backspace: "⌫",
  escape: "⎋",
  tab: "⇥",
  capslock: "⇪",
  arrowup: "↑",
  arrowright: "→",
  arrowdown: "↓",
  arrowleft: "←",
  pageup: "⇞",
  pagedown: "⇟",
  home: "↖",
  end: "↘",
};

export type KbdKey = keyof typeof kbdKeysMap;
export type KbdKeySpecific = keyof KbdKeysSpecificMap;

const _useKbd = () => {
  const macOS = computed(
    () =>
      import.meta.client &&
      navigator &&
      navigator.userAgent &&
      navigator.userAgent.match(/Macintosh;/)
  );

  const kbdKeysSpecificMap = reactive({
    meta: " ",
    alt: " ",
    ctrl: " ",
  });

  onMounted(() => {
    kbdKeysSpecificMap.meta = macOS.value ? kbdKeysMap.command : "Ctrl";
    kbdKeysSpecificMap.ctrl = macOS.value ? kbdKeysMap.control : "Ctrl";
    kbdKeysSpecificMap.alt = macOS.value ? kbdKeysMap.option : "Alt";
  });

  function getKbdKey(value?: KbdKey | string) {
    if (!value) {
      return;
    }

    if (["meta", "alt", "ctrl"].includes(value)) {
      return kbdKeysSpecificMap[value as KbdKeySpecific];
    }

    return kbdKeysMap[value as KbdKey] || value.toUpperCase();
  }

  return {
    macOS,
    getKbdKey,
  };
};

export const useKbd = /* @__PURE__ */ createSharedComposable(_useKbd);

type Handler = (e?: any) => void;

export interface ShortcutConfig {
  handler: Handler;
  usingInput?: string | boolean;
}

export interface ShortcutsConfig {
  [key: string]: ShortcutConfig | Handler | false | null | undefined;
}

export interface ShortcutsOptions {
  chainDelay?: number;
}

interface Shortcut {
  handler: Handler;
  enabled: boolean;
  chained: boolean;
  // KeyboardEvent attributes
  key: string;
  ctrlKey: boolean;
  metaKey: boolean;
  shiftKey: boolean;
  altKey: boolean;
  // code?: string
  // keyCode?: number
}

const chainedShortcutRegex = /^[^-]+.*-.*[^-]+$/;
const combinedShortcutRegex = /^[^_]+.*_.*[^_]+$/;
// keyboard keys which can be combined with Shift modifier (in addition to alphabet keys)
const shiftableKeys = [
  "arrowleft",
  "arrowright",
  "arrowup",
  "arrowright",
  "tab",
  "escape",
  "enter",
  "backspace",
];

export function extractShortcuts(items: any[] | any[][]) {
  const shortcuts: Record<string, Handler> = {};

  function traverse(items: any[]) {
    items.forEach((item) => {
      if (item.kbds?.length && (item.onSelect || item.onClick)) {
        const shortcutKey = item.kbds.join("_");
        shortcuts[shortcutKey] = item.onSelect || item.onClick;
      }
      if (item.children) {
        traverse(item.children.flat());
      }
      if (item.items) {
        traverse(item.items.flat());
      }
    });
  }

  traverse(items.flat());

  return shortcuts;
}

export function defineShortcuts(config: MaybeRef<ShortcutsConfig>, options: ShortcutsOptions = {}) {
  const chainedInputs = ref<string[]>([]);
  const clearChainedInput = () => {
    chainedInputs.value.splice(0, chainedInputs.value.length);
  };
  const debouncedClearChainedInput = useDebounceFn(clearChainedInput, options.chainDelay ?? 800);

  const { macOS } = useKbd();
  const activeElement = useActiveElement();

  const onKeyDown = (e: KeyboardEvent) => {
    // Input autocomplete triggers a keydown event
    if (!e.key) {
      return;
    }

    const alphabetKey = /^[a-z]{1}$/i.test(e.key);
    const shiftableKey = shiftableKeys.includes(e.key.toLowerCase());

    let chainedKey;
    chainedInputs.value.push(e.key);
    // try matching a chained shortcut
    if (chainedInputs.value.length >= 2) {
      chainedKey = chainedInputs.value.slice(-2).join("-");

      for (const shortcut of shortcuts.value.filter((s) => s.chained)) {
        if (shortcut.key !== chainedKey) {
          continue;
        }

        if (shortcut.enabled) {
          e.preventDefault();
          shortcut.handler(e);
        }
        clearChainedInput();
        return;
      }
    }

    // try matching a standard shortcut
    for (const shortcut of shortcuts.value.filter((s) => !s.chained)) {
      if (e.key.toLowerCase() !== shortcut.key) {
        continue;
      }
      if (e.metaKey !== shortcut.metaKey) {
        continue;
      }
      if (e.ctrlKey !== shortcut.ctrlKey) {
        continue;
      }
      // shift modifier is only checked in combination with alphabet keys and some extra keys
      // (shift with special characters would change the key)
      if ((alphabetKey || shiftableKey) && e.shiftKey !== shortcut.shiftKey) {
        continue;
      }
      // alt modifier changes the combined key anyways
      // if (e.altKey !== shortcut.altKey) { continue }

      if (shortcut.enabled) {
        e.preventDefault();
        shortcut.handler(e);
      }
      clearChainedInput();
      return;
    }

    debouncedClearChainedInput();
  };

  const usingInput = computed(() => {
    const tagName = activeElement.value?.tagName;
    const contentEditable = activeElement.value?.contentEditable;

    const usingInput = !!(
      tagName === "INPUT" ||
      tagName === "TEXTAREA" ||
      contentEditable === "true" ||
      contentEditable === "plaintext-only"
    );

    if (usingInput) {
      return ((activeElement.value as any)?.name as string) || true;
    }

    return false;
  });

  // Map config to full detailed shortcuts
  const shortcuts = computed<Shortcut[]>(() => {
    return Object.entries(toValue(config))
      .map(([key, shortcutConfig]) => {
        if (!shortcutConfig) {
          return null;
        }

        // Parse key and modifiers
        let shortcut: Partial<Shortcut>;

        if (
          key.includes("-") &&
          key !== "-" &&
          !key.includes("_") &&
          !key.match(chainedShortcutRegex)?.length
        ) {
          console.trace(\`[Shortcut] Invalid key: "\${key}"\`);
        }

        if (key.includes("_") && key !== "_" && !key.match(combinedShortcutRegex)?.length) {
          console.trace(\`[Shortcut] Invalid key: "\${key}"\`);
        }

        const chained = key.includes("-") && key !== "-" && !key.includes("_");
        if (chained) {
          shortcut = {
            key: key.toLowerCase(),
            metaKey: false,
            ctrlKey: false,
            shiftKey: false,
            altKey: false,
          };
        } else {
          const keySplit = key
            .toLowerCase()
            .split("_")
            .map((k) => k);
          shortcut = {
            key: keySplit
              .filter((k) => !["meta", "command", "ctrl", "shift", "alt", "option"].includes(k))
              .join("_"),
            metaKey: keySplit.includes("meta") || keySplit.includes("command"),
            ctrlKey: keySplit.includes("ctrl"),
            shiftKey: keySplit.includes("shift"),
            altKey: keySplit.includes("alt") || keySplit.includes("option"),
          };
        }
        shortcut.chained = chained;

        // Convert Meta to Ctrl for non-MacOS
        if (!macOS.value && shortcut.metaKey && !shortcut.ctrlKey) {
          shortcut.metaKey = false;
          shortcut.ctrlKey = true;
        }

        // Retrieve handler function
        if (typeof shortcutConfig === "function") {
          shortcut.handler = shortcutConfig;
        } else if (typeof shortcutConfig === "object") {
          shortcut = { ...shortcut, handler: shortcutConfig.handler };
        }

        if (!shortcut.handler) {
          console.trace("[Shortcut] Invalid value");
          return null;
        }

        let enabled = true;
        if (!(shortcutConfig as ShortcutConfig).usingInput) {
          enabled = !usingInput.value;
        } else if (typeof (shortcutConfig as ShortcutConfig).usingInput === "string") {
          enabled = usingInput.value === (shortcutConfig as ShortcutConfig).usingInput;
        }
        shortcut.enabled = enabled;

        return shortcut;
      })
      .filter(Boolean) as Shortcut[];
  });

  return useEventListener("keydown", onKeyDown);
}

`;
