const letter = (ch) => ({ id: ch.toUpperCase(), label: ch });
const symbol = (ch) => ({ id: ch, label: ch });

const KEY_ROWS = [
  [
    symbol("'"),
    ..."1234567890".split("").map((ch) => symbol(ch)),
    symbol("-"),
    symbol("="),
    { id: "BACKSP", label: "Backsp", width: 2 },
  ],
  [
    { id: "TAB", label: "Tab", width: 2 },
    ..."qwertyuiop".split("").map(letter),
    symbol("["),
    symbol("]"),
    { id: "\\", label: "\\", width: 2 },
  ],
  [
    { id: "CAPS", label: "Caps", width: 2 },
    ..."asdfghjkl".split("").map(letter),
    symbol(";"),
    symbol("#"),
    { id: "ENTER", label: "Enter", width: 2 },
  ],
  [
    { id: "SHIFT", label: "Shift", width: 2 },
    ..."zxcvbnm".split("").map(letter),
    symbol(","),
    symbol("."),
    symbol("/"),
    { id: "SHIFT_R", label: "Shift", width: 2 },
  ],
  [{ id: "SPACE", label: "", width: 1, space: true }],
];

const KEYBOARD_TEMPLATE = `
<section class="overlay-keyboard" data-overlay-keyboard hidden>
  <div class="overlay-keyboard-inner">
    <div class="overlay-keyboard-rows" data-overlay-keyboard-rows></div>
  </div>
</section>
`;

const clamp = (value, min, max) => Math.max(min, Math.min(max, value));

const isLetter = (key) => /^[A-Z]$/.test(key);

export const createKeyboardOverlay = ({ mountRoot }) => {
  mountRoot.innerHTML = KEYBOARD_TEMPLATE;

  const root = mountRoot.querySelector("[data-overlay-keyboard]");
  const rowsRoot = mountRoot.querySelector("[data-overlay-keyboard-rows]");
  const keyButtons = [];
  let openState = false;
  let selectedRow = 0;
  let selectedCol = 0;
  let handlers = null;
  let capsLock = false;
  let shiftActive = false;
  let capsButton = null;
  const shiftButtons = [];

  const rowWidth = (rowIndex) => KEY_ROWS[rowIndex]?.length ?? 0;

  const lettersAreUppercase = () => capsLock !== shiftActive;

  const applySelection = () => {
    keyButtons.forEach((button) => button.classList.remove("is-focused"));
    const selected = keyButtons.find(
      (button) =>
        Number(button.dataset.row) === selectedRow && Number(button.dataset.col) === selectedCol,
    );
    selected?.classList.add("is-focused");
  };

  const currentKey = () => {
    const selected = keyButtons.find(
      (button) =>
        Number(button.dataset.row) === selectedRow && Number(button.dataset.col) === selectedCol,
    );
    return selected?.dataset.key ?? null;
  };

  const updateModifierState = () => {
    const uppercase = lettersAreUppercase();

    root?.classList.toggle("is-uppercase", uppercase);
    capsButton?.classList.toggle("is-active", capsLock);
    shiftButtons.forEach((button) => button.classList.toggle("is-active", shiftActive));

    keyButtons.forEach((button) => {
      const key = button.dataset.key;
      if (!isLetter(key)) {
        return;
      }
      const base = button.dataset.baseLabel ?? key.toLowerCase();
      button.textContent = uppercase ? base.toUpperCase() : base;
    });
  };

  const resolveOutput = (key) => {
    if (key === "SPACE") {
      return " ";
    }
    if (isLetter(key)) {
      return lettersAreUppercase() ? key : key.toLowerCase();
    }
    return key;
  };

  const consumeShift = () => {
    if (!shiftActive) {
      return;
    }
    shiftActive = false;
    updateModifierState();
  };

  const build = () => {
    if (!rowsRoot) {
      return;
    }
    rowsRoot.innerHTML = "";
    keyButtons.length = 0;
    capsButton = null;
    shiftButtons.length = 0;

    KEY_ROWS.forEach((row, rowIndex) => {
      const rowEl = document.createElement("div");
      rowEl.className = "overlay-keyboard-row";
      if (row.some((key) => key.space)) {
        rowEl.classList.add("is-space-row");
      }

      row.forEach((keyDef, colIndex) => {
        const button = document.createElement("button");
        button.type = "button";
        button.className = "overlay-keyboard-key";
        button.dataset.row = String(rowIndex);
        button.dataset.col = String(colIndex);
        button.dataset.key = keyDef.id;
        button.dataset.width = String(keyDef.width ?? 1);
        if (isLetter(keyDef.id)) {
          button.dataset.baseLabel = (keyDef.label ?? keyDef.id).toLowerCase();
          button.classList.add("is-letter");
        }
        button.textContent = keyDef.label ?? keyDef.id;

        if (keyDef.width && keyDef.width > 1) {
          button.classList.add("is-wide");
        }
        if (keyDef.space) {
          button.classList.add("is-space");
          button.setAttribute("aria-label", "Space");
        }
        if (keyDef.id === "ENTER") {
          button.classList.add("is-enter");
        }
        if (keyDef.id === "CAPS") {
          button.classList.add("is-modifier");
          capsButton = button;
        }
        if (keyDef.id === "SHIFT" || keyDef.id === "SHIFT_R") {
          button.classList.add("is-modifier");
          shiftButtons.push(button);
        }

        rowEl.append(button);
        keyButtons.push(button);
      });

      rowsRoot.append(rowEl);
    });

    updateModifierState();
  };

  build();

  const open = (nextHandlers = {}) => {
    handlers = nextHandlers;
    openState = true;
    capsLock = false;
    shiftActive = false;
    selectedRow = 2;
    selectedCol = 1;
    root.hidden = false;
    updateModifierState();
    applySelection();
  };

  const close = () => {
    openState = false;
    root.hidden = true;
    handlers = null;
    capsLock = false;
    shiftActive = false;
    updateModifierState();
  };

  const move = (direction) => {
    if (direction === "left") {
      selectedCol = clamp(selectedCol - 1, 0, Math.max(0, rowWidth(selectedRow) - 1));
      return;
    }
    if (direction === "right") {
      selectedCol = clamp(selectedCol + 1, 0, Math.max(0, rowWidth(selectedRow) - 1));
      return;
    }
    if (direction === "up") {
      selectedRow = clamp(selectedRow - 1, 0, KEY_ROWS.length - 1);
      selectedCol = clamp(selectedCol, 0, Math.max(0, rowWidth(selectedRow) - 1));
      return;
    }
    if (direction === "down") {
      selectedRow = clamp(selectedRow + 1, 0, KEY_ROWS.length - 1);
      selectedCol = clamp(selectedCol, 0, Math.max(0, rowWidth(selectedRow) - 1));
    }
  };

  const activateKey = (key) => {
    if (!key) {
      return;
    }

    if (key === "ENTER") {
      handlers?.onSubmit?.();
      return;
    }
    if (key === "BACKSP") {
      handlers?.onBackspace?.();
      return;
    }
    if (key === "TAB") {
      handlers?.onNextField?.();
      return;
    }
    if (key === "CAPS") {
      capsLock = !capsLock;
      updateModifierState();
      return;
    }
    if (key === "SHIFT" || key === "SHIFT_R") {
      shiftActive = !shiftActive;
      updateModifierState();
      return;
    }
    if (key === "SPACE") {
      handlers?.onKey?.(" ");
      consumeShift();
      return;
    }

    const output = resolveOutput(key);
    if (output) {
      handlers?.onKey?.(output);
      consumeShift();
    }
  };

  const handleAction = (action) => {
    if (!openState) {
      return false;
    }

    if (action === "up" || action === "down" || action === "left" || action === "right") {
      move(action);
      applySelection();
      return true;
    }

    if (action === "accept") {
      activateKey(currentKey());
      return true;
    }

    if (action === "back") {
      handlers?.onBackspace?.();
      return true;
    }

    if (action === "menu") {
      handlers?.onSubmit?.();
      return true;
    }

    if (action === "clear") {
      handlers?.onClear?.();
      return true;
    }

    if (action === "l1") {
      handlers?.onPrevField?.();
      return true;
    }

    if (action === "r1") {
      handlers?.onNextField?.();
      return true;
    }

    return false;
  };

  return {
    open,
    close,
    isOpen: () => openState,
    handleAction,
  };
};
