export const DEFINE_SHORTCUT = `/* eslint-disable @typescript-eslint/no-unsafe-function-type */
import { logicAnd, logicNot } from "@vueuse/math";
import type { ComputedRef, WatchSource } from "vue";

export interface ShortcutConfig {
  handler: Function;
  usingInput?: string | boolean;
  whenever?: WatchSource<boolean>[];
}

export interface ShortcutsConfig {
  [key: string]: ShortcutConfig | Function;
}

export interface ShortcutsOptions {
  chainDelay?: number;
}

interface Shortcut {
  handler: Function;
  condition: ComputedRef<boolean>;
  chained: boolean;
  // KeyboardEvent attributes
  key: string;
  ctrlKey: boolean;
  metaKey: boolean;
  shiftKey: boolean;
  altKey: boolean;
  // code?: string
  // keyCode?: number
}

export const defineShortcuts = (config: ShortcutsConfig, options: ShortcutsOptions = {}) => {
  const { macOS, usingInput } = useShortcuts();

  let shortcuts: Shortcut[] = [];

  const chainedInputs = ref<any[]>([]);
  const clearChainedInput = () => {
    chainedInputs.value.splice(0, chainedInputs.value.length);
  };
  const debouncedClearChainedInput = useDebounceFn(clearChainedInput, options.chainDelay ?? 800);

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

    const alphabeticalKey = /^[a-z]{1}$/i.test(e.key);

    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.filter((s) => s.chained)) {
        if (shortcut.key !== chainedKey) {
          continue;
        }

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

    // try matching a standard shortcut
    for (const shortcut of shortcuts.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 alphabetical keys
      // (shift with non-alphabetical keys would change the key)
      if (alphabeticalKey && e.shiftKey !== shortcut.shiftKey) {
        continue;
      }
      // alt modifier changes the combined key anyways
      // if (e.altKey !== shortcut.altKey) { continue }

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

    debouncedClearChainedInput();
  };

  // Map config to full detailled shortcuts
  shortcuts = Object.entries(config)
    .map(([key, shortcutConfig]) => {
      if (!shortcutConfig) {
        return null;
      }

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

      if (key.includes("-") && key.includes("_")) {
        console.trace("[Shortcut] Invalid key");
        return null;
      }

      const chained = 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", "ctrl", "shift", "alt"].includes(k)).join("_"),
          metaKey: keySplit.includes("meta"),
          ctrlKey: keySplit.includes("ctrl"),
          shiftKey: keySplit.includes("shift"),
          altKey: keySplit.includes("alt"),
        };
      }
      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;
      }

      // Create shortcut computed
      const conditions: ComputedRef<boolean>[] = [];
      if (!(shortcutConfig as ShortcutConfig).usingInput) {
        conditions.push(logicNot(usingInput));
      } else if (typeof (shortcutConfig as ShortcutConfig).usingInput === "string") {
        conditions.push(
          computed(() => usingInput.value === (shortcutConfig as ShortcutConfig).usingInput)
        );
      }
      shortcut.condition = logicAnd(
        ...conditions,
        ...((shortcutConfig as ShortcutConfig).whenever || [])
      );

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

  useEventListener("keydown", onKeyDown);
};

`;

export const USE_SHORTCUTS = `export const _useShortcuts = () => {
  const macOS = computed(
    () =>
      import.meta.client &&
      navigator &&
      navigator.userAgent &&
      navigator.userAgent.match(/Macintosh;/)
  );

  const metaSymbol = ref(" ");

  const activeElement = useActiveElement();
  const usingInput = computed(() => {
    const usingInput = !!(
      activeElement.value?.tagName === "INPUT" ||
      activeElement.value?.tagName === "TEXTAREA" ||
      activeElement.value?.contentEditable === "true"
    );

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

    return false;
  });

  onMounted(() => {
    metaSymbol.value = macOS.value ? "⌘" : "Ctrl";
  });

  return {
    macOS,
    metaSymbol,
    activeElement,
    usingInput,
  };
};

export const useShortcuts = createSharedComposable(_useShortcuts);

`;
