/**
 * On-screen keyboard for setup wizard on touch kiosks (no system IME).
 * Uses wesp-setup CSS variables for light/dark theme.
 */

const LAYOUTS = {
  hostname: [
    ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"],
    ["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"],
    [
      "a",
      "s",
      "d",
      "f",
      "g",
      "h",
      "j",
      "k",
      "l",
      { key: "backspace", label: "⌫", span: 2 },
    ],
    ["z", "x", "c", "v", "b", "n", "m", "_", "-", "."],
  ],
  url: [
    ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"],
    ["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"],
    [
      "a",
      "s",
      "d",
      "f",
      "g",
      "h",
      "j",
      "k",
      "l",
      { key: "backspace", label: "⌫", span: 2 },
    ],
    ["z", "x", "c", "v", "b", "n", "m", "_", "-", "."],
    [
      { key: "http://", label: "http://", span: 3 },
      { key: ":", label: ":" },
      { key: "/", label: "/" },
      { key: "backspace", label: "⌫", span: 2 },
    ],
  ],
};

const HOSTNAME_CHAR = /[a-z0-9._-]/;
const URL_CHAR = /[a-z0-9.:/_-]/;

function allowedChar(ch, layout) {
  if (layout === "url") return URL_CHAR.test(ch);
  return HOSTNAME_CHAR.test(ch);
}

function normalizeInsert(ch, layout) {
  const lower = ch.toLowerCase();
  if (ch.length === 1 && !allowedChar(lower, layout)) return "";
  return lower;
}

export class SetupTouchKeyboard {
  #panel;
  #root = null;
  #grid = null;
  #activeInput = null;
  #activeLayout = "hostname";
  #bindings = new Map();
  #docClickHandler = null;
  #maxLength = 200;

  constructor(panel) {
    this.#panel = panel;
  }

  attach(input, { layout = "hostname", maxLength = 200 } = {}) {
    if (!input || input.disabled) return;
    this.#bindings.set(input, { layout, maxLength });
    input.readOnly = true;
    input.inputMode = "none";
    input.autocomplete = "off";
    input.autocorrect = "off";
    input.spellcheck = false;
    input.classList.add("wesp-setup__input--touch");

    const onFocus = () => this.#showFor(input);
    input.addEventListener("focus", onFocus);
    input.addEventListener("click", onFocus);
  }

  mount() {
    if (this.#root) return;
    this.#root = document.createElement("div");
    this.#root.className = "wesp-setup__keyboard";
    this.#root.setAttribute("role", "group");
    this.#root.setAttribute("aria-label", "Экранная клавиатура");

    this.#grid = document.createElement("div");
    this.#grid.className = "wesp-setup__keyboard-grid";
    this.#root.appendChild(this.#grid);

    const footer = this.#panel.querySelector(".wesp-setup__footer");
    if (footer) {
      this.#panel.insertBefore(this.#root, footer);
    } else {
      this.#panel.appendChild(this.#root);
    }

    this.#docClickHandler = (e) => {
      if (!this.#root?.classList.contains("is-visible")) return;
      const t = e.target;
      if (this.#root.contains(t)) return;
      if (this.#activeInput && (t === this.#activeInput || this.#activeInput.contains?.(t))) return;
      for (const input of this.#bindings.keys()) {
        if (t === input || input.contains?.(t)) return;
      }
      this.hide();
    };
    document.addEventListener("pointerdown", this.#docClickHandler, true);
  }

  hide() {
    this.#root?.classList.remove("is-visible");
    this.#activeInput = null;
  }

  destroy() {
    if (this.#docClickHandler) {
      document.removeEventListener("pointerdown", this.#docClickHandler, true);
      this.#docClickHandler = null;
    }
    this.#root?.remove();
    this.#root = null;
    this.#grid = null;
    this.#bindings.clear();
    this.#activeInput = null;
  }

  #showFor(input) {
    const binding = this.#bindings.get(input);
    if (!binding) return;
    this.mount();
    this.#activeInput = input;
    this.#activeLayout = binding.layout;
    this.#maxLength = binding.maxLength;
    this.#renderKeys(binding.layout);
    this.#root.classList.add("is-visible");
    window.requestAnimationFrame(() => {
      input.scrollIntoView({ block: "nearest", behavior: "smooth" });
    });
  }

  #renderKeys(layoutName) {
    const rows = LAYOUTS[layoutName] || LAYOUTS.hostname;
    this.#grid.replaceChildren();
    rows.forEach((row) => {
      row.forEach((item) => {
        const spec = typeof item === "string" ? { key: item, label: item } : item;
        const btn = document.createElement("button");
        btn.type = "button";
        btn.className = "wesp-setup__keyboard-key";
        if (spec.key === "backspace") btn.classList.add("wesp-setup__keyboard-key--wide");
        if (spec.key === "http://") btn.classList.add("wesp-setup__keyboard-key--action");
        btn.textContent = spec.label || spec.key;
        const span = spec.span || 1;
        if (span > 1) btn.style.gridColumn = `span ${span}`;
        btn.addEventListener("pointerdown", (e) => {
          e.preventDefault();
          this.#onKey(spec.key);
        });
        this.#grid.appendChild(btn);
      });
    });
  }

  #onKey(key) {
    const input = this.#activeInput;
    if (!input) return;
    if (key === "backspace") {
      input.value = input.value.slice(0, -1);
      input.dispatchEvent(new Event("input", { bubbles: true }));
      return;
    }
    let insert = "";
    if (key === "http://") {
      if (this.#activeLayout === "url") insert = "http://";
    } else {
      insert = normalizeInsert(key, this.#activeLayout);
    }
    if (!insert) return;
    if (input.value.length >= this.#maxLength) return;
    const start = input.selectionStart ?? input.value.length;
    const end = input.selectionEnd ?? start;
    input.value = input.value.slice(0, start) + insert + input.value.slice(end);
    const pos = start + insert.length;
    try {
      input.setSelectionRange(pos, pos);
    } catch {
      /* readonly selection may fail in some browsers */
    }
    input.dispatchEvent(new Event("input", { bubbles: true }));
  }
}
