(() => {
  "use strict";

  const API = {
    list: "/api/keys",
    register: "/api/keys/register",
    delete: (id) => `/api/keys/${encodeURIComponent(id)}`,
    reveal: (id) => `/api/keys/${encodeURIComponent(id)}/access-key`,
  };

  let bound = false;

  function qs(sel, root = document) {
    return root.querySelector(sel);
  }

  function esc(v) {
    return String(v ?? "").replace(/[&<>"']/g, ch => ({
      "&": "&amp;",
      "<": "&lt;",
      ">": "&gt;",
      "\"": "&quot;",
      "'": "&#39;",
    }[ch]));
  }

  async function readJson(res) {
    const text = await res.text();
    if (!text) return {};
    try {
      return JSON.parse(text);
    } catch {
      return { error: text };
    }
  }

  function formEl() {
    const version = qs("#key-version");
    return version ? version.closest("form") : null;
  }

  function listEl() {
    return qs(".key-list");
  }

  function setMessage(text, kind = "") {
    const form = formEl();
    if (!form) return;

    let box = qs("#key-profile-status");
    if (!box) {
      box = document.createElement("p");
      box.id = "key-profile-status";
      box.className = "key-status-message";
      form.insertAdjacentElement("afterend", box);
    }

    box.textContent = text || "";
    box.dataset.kind = kind;
  }

  function normalizeSerial(raw, gameVersion) {
    const value = String(raw || "").trim();

    if (gameVersion === "dc_v2") {
      let v = value.toUpperCase();
      if (v.startsWith("0X")) v = v.slice(2);
      if (/^[0-9A-F]{1,8}$/.test(v)) {
        return v.padStart(8, "0");
      }
      return "";
    }

    if (gameVersion === "pc_v2") {
      if (/^[0-9]{1,10}$/.test(value)) {
        const n = Number(value);
        if (Number.isSafeInteger(n) && n >= 0 && n <= 0xFFFFFFFF) {
          return n.toString(16).toUpperCase().padStart(8, "0");
        }
      }
      return "";
    }

    if (gameVersion === "gc_v3") {
      if (/^[0-9]{2}-[0-9]{4}-[0-9]{4}$/.test(value)) {
        const n = Number(value.replaceAll("-", ""));
        if (Number.isSafeInteger(n) && n >= 0 && n <= 0xFFFFFFFF) {
          return n.toString(16).toUpperCase().padStart(8, "0");
        }
      }
      return "";
    }

    return "";
  }

  function setupForm(form) {
    const version = qs("#key-version", form);
    if (version) {
      version.innerHTML = [
        '<option value="dc_v2">DC V2</option>',
        '<option value="pc_v2">PC V2</option>',
        '<option value="gc_v3">GC V3</option>',
      ].join("");
    }

    updateSerialHint(form);

    const versionSelect = qs("#key-version", form);
    if (versionSelect && !versionSelect.dataset.hintBound) {
      versionSelect.dataset.hintBound = "1";
      versionSelect.addEventListener("change", () => updateSerialHint(form));
    }

    const keyPassword = qs("#key-password", form);
    if (keyPassword) {
      const label = qs('label[for="key-password"]', form);
      if (label) label.remove();
      keyPassword.remove();
    }

    const serial = qs("#key-serial", form);
    if (serial && !qs("#key-display-serial", form)) {
      const displayLabel = document.createElement("label");
      displayLabel.id = "key-display-serial-label";
      displayLabel.setAttribute("for", "key-display-serial");
      displayLabel.textContent = "Confirm Serial Number";

      const displayInput = document.createElement("input");
      displayInput.id = "key-display-serial";
      displayInput.name = "display_serial";
      displayInput.type = "text";
      displayInput.autocomplete = "off";
      displayInput.placeholder = "confirm serial number";

      serial.insertAdjacentElement("afterend", displayInput);
      serial.insertAdjacentElement("afterend", displayLabel);
    }

    const access = qs("#key-access", form);
    if (access && !qs("#key-password", form)) {
      const passwordLabel = document.createElement("label");
      passwordLabel.id = "key-password-label";
      passwordLabel.setAttribute("for", "key-password");
      passwordLabel.textContent = "GC Password";
      passwordLabel.style.display = "none";

      const passwordInput = document.createElement("input");
      passwordInput.id = "key-password";
      passwordInput.name = "password";
      passwordInput.type = "text";
      passwordInput.autocomplete = "off";
      passwordInput.placeholder = "GC password";
      passwordInput.style.display = "none";

      access.insertAdjacentElement("afterend", passwordInput);
      access.insertAdjacentElement("afterend", passwordLabel);
    }

    const button = qs('button[type="submit"]', form) || qs("button", form);
    if (button) {
      button.type = "button";
    }
  }


  function syncDotClass(status) {
    const value = String(status || "").toLowerCase();
    return (value === "synced" || value === "current") ? "is-synced" : "is-syncing";
  }

  function renderSyncStatus(label, status) {
    const safeStatus = status || "unknown";
    return `
      <span class="key-sync-status">
        <span class="key-sync-dot ${syncDotClass(safeStatus)}" style="color: ${syncDotClass(safeStatus) === 'is-synced' ? '#38d66b' : '#e05252'};" aria-hidden="true">●</span>
        <span>${esc(label)}: <strong>${esc(safeStatus)}</strong></span>
      </span>
    `;
  }

  function renderKeySyncSummary(data) {
    const box = qs("#key-sync-summary");
    if (!box) return;

    const sync = data.sync_status || "unknown";
    const us = data.regions?.us?.status || "unknown";
    const eu = data.regions?.eu?.status || "unknown";

    box.innerHTML = `
      ${renderSyncStatus("Key sync", sync)}
      ${renderSyncStatus("US", us)}
      ${renderSyncStatus("EU", eu)}
    `;
  }


  async function loadKeys() {
    const res = await fetch("/api/keys", {
      credentials: "same-origin",
    });

    const data = await readJson(res);

    if (!res.ok || data.ok === false) {
      throw new Error(data.error || data.detail || `HTTP ${res.status}`);
    }

    renderKeys(data);
    return data;
  }


  function renderKeys(data) {
    const list = listEl();
    if (!list) return;

    const keys = Array.isArray(data.keys) ? data.keys : [];
    const sync = data.sync_status || "unknown";
    const us = data.regions?.us?.status || "unknown";
    const eu = data.regions?.eu?.status || "unknown";

    renderKeySyncSummary(data);
    const summary = "";

    if (!keys.length) {
      list.innerHTML = summary + `
        <article class="key-row key-row--empty" role="listitem">
          <div>
            <h3>No V2 / V3 keys registered</h3>
            <p>Add a DC V2, PC V2, or GC V3 key profile above.</p>
          </div>
        </article>
      `;
      return;
    }

    list.innerHTML = summary + keys.map(key => {
      const version = key.game_version_label || key.game_version || "Key";
      const label = key.label || "";
      const serial = key.serial_number_hex || "";
      let displaySerial = key.display_serial || serial;
      if (!key.display_serial && serial) {
        const n = Number.parseInt(serial, 16);
        if (Number.isFinite(n)) {
          if (key.game_version === "pc_v2") {
            displaySerial = String(n);
          } else if (key.game_version === "gc_v3") {
            const dec = String(n).padStart(10, "0");
            displaySerial = `${dec.slice(0, 2)}-${dec.slice(2, 6)}-${dec.slice(6, 10)}`;
          }
        }
      }
      const isGC = key.game_version === "gc_v3";

      return `
        <article class="key-row" role="listitem" data-key-id="${esc(key.id)}" data-key-version="${esc(key.game_version || "")}">
          <div>
            <h3>${esc(version)}</h3>
            <p>Registered profile <strong>${esc(serial)}</strong></p>
            <p class="key-meta">Serial: ${esc(displaySerial)}</p>
            ${label ? `<p class="key-meta">Label: ${esc(label)}</p>` : ""}
            <p class="key-secret-line">
              <span>Access key:</span>
              <code class="key-secret-value" data-key-secret="${esc(key.id)}" data-key-secret-kind="access_key">••••••••••••</code>
              <button class="inline-link key-reveal-button" type="button" data-key-id="${esc(key.id)}">
                show
              </button>
            </p>
            ${isGC ? `<p class="key-secret-line">
              <span>Password:</span>
              <code class="key-secret-value" data-key-secret="${esc(key.id)}" data-key-secret-kind="password">••••••••••••</code>
            </p>` : ""}
          </div>
          <button class="button-danger key-delete-button" type="button" data-key-id="${esc(key.id)}">
            Delete Key
          </button>
        </article>
      `;
    }).join("");
  }


  function updateSerialHint(form) {
    const gameVersion = qs("#key-version", form)?.value || "";
    const serial = qs("#key-serial", form);
    const displaySerial = qs("#key-display-serial", form);
    const access = qs("#key-access", form);
    const password = qs("#key-password", form);
    const passwordLabel = qs("#key-password-label", form);

    if (!serial) return;

    if (gameVersion === "dc_v2") {
      serial.placeholder = "DC V2 serial number";
      if (displaySerial) displaySerial.placeholder = "confirm DC V2 serial number";
      if (access) access.placeholder = "access key";
    } else if (gameVersion === "pc_v2") {
      serial.placeholder = "PC V2 decimal serial number";
      if (displaySerial) displaySerial.placeholder = "confirm PC V2 decimal serial number";
      if (access) access.placeholder = "access key";
    } else if (gameVersion === "gc_v3") {
      serial.placeholder = "GC V3 serial number";
      if (displaySerial) displaySerial.placeholder = "confirm GC V3 serial number";
      if (access) access.placeholder = "access key";
    }

    if (passwordLabel) {
      passwordLabel.style.display = gameVersion === "gc_v3" ? "" : "none";
    }
    if (password) {
      password.style.display = gameVersion === "gc_v3" ? "" : "none";
      if (gameVersion !== "gc_v3") {
        password.value = "";
      }
    }
  }

  async function registerKey() {
    const form = formEl();
    if (!form) return;

    const button = qs("button", form);
    const gameVersion = qs("#key-version", form)?.value || "";
    const label = qs("#key-label", form)?.value.trim() || "";
    const rawSerial = qs("#key-serial", form)?.value.trim() || "";
    const displaySerial = qs("#key-display-serial", form)?.value.trim() || "";
    const accessKey = qs("#key-access", form)?.value.trim() || "";
    const password = qs("#key-password", form)?.value.trim() || "";

    if (!["dc_v2", "pc_v2", "gc_v3"].includes(gameVersion)) {
      setMessage("Choose DC V2, PC V2, or GC V3.", "warn");
      return;
    }

    const serialChecks = {
      dc_v2: /^(?:0x)?[0-9A-Fa-f]{1,8}$/,
      pc_v2: /^[0-9]{1,10}$/,
      gc_v3: /^[0-9]{2}-[0-9]{4}-[0-9]{4}$/,
    };

    if (!serialChecks[gameVersion]?.test(rawSerial)) {
      const examples = {
        dc_v2: "DC V2 serial must be hex, like 4E62F237.",
        pc_v2: "PC V2 serial must be digits only.",
        gc_v3: "GC V3 serial must use the dashed format: NN-NNNN-NNNN.",
      };
      setMessage(examples[gameVersion] || "Enter a valid serial number.", "warn");
      return;
    }

    const serial = rawSerial;

    if (!serial) {
      setMessage("Enter a valid serial number.", "warn");
      return;
    }

    if (!displaySerial) {
      setMessage("Confirm serial number is required.", "warn");
      return;
    }

    if (displaySerial !== rawSerial) {
      setMessage("Serial number confirmation does not match.", "warn");
      return;
    }

    if (!accessKey) {
      setMessage("Enter the key.", "warn");
      return;
    }

    if (gameVersion === "gc_v3" && !password) {
      setMessage("Enter the GC password.", "warn");
      return;
    }

    if (button) button.disabled = true;
    setMessage("Registering key profile...", "pending");

    try {
      const res = await fetch("/api/keys/register", {
        method: "POST",
        credentials: "same-origin",
        headers: {
          "Accept": "application/json",
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          game_version: gameVersion,
          label,
          serial_number: serial,
          display_serial: displaySerial,
          access_key: accessKey,
          password,
        }),
      });

      const data = await readJson(res);
      if (!res.ok) {
        throw new Error(data.error || `Register failed: ${res.status}`);
      }

      form.reset();
      setupForm(form);
      setMessage(`Saved. Sync status: ${data.sync_status}. Refresh status in a few seconds.`, "ok");
      await loadKeys();
    } catch (err) {
      setMessage(err.message || String(err), "warn");
    } finally {
      if (button) button.disabled = false;
    }
  }


  async function revealAccessKey(id, button) {
    if (!id || !button) return;

    const card = button.closest("article, .key-card, .key-row, .registered-key");
    const values = Array.from(card?.querySelectorAll(`[data-key-secret="${String(id).replace(/"/g, "\\\"")}"]`) || []);
    if (!values.length) {
      setMessage("Could not find this key on the page. Refresh and try again.", "warn");
      return;
    }

    if (button.dataset.visible === "1") {
      values.forEach(value => value.textContent = "••••••••••••");
      button.textContent = "show";
      button.dataset.visible = "0";
      return;
    }

    button.disabled = true;

    try {
      const res = await fetch(API.reveal(id), {
        credentials: "same-origin",
        headers: { "Accept": "application/json" },
      });

      const data = await readJson(res);
      if (!res.ok) {
        throw new Error(data.error || `Reveal failed: ${res.status}`);
      }

      values.forEach(value => {
        const kind = value.dataset.keySecretKind || "access_key";
        value.textContent = data.key?.[kind] || "";
      });

      button.textContent = "hide";
      button.dataset.visible = "1";
    } catch (err) {
      setMessage(err.message || String(err), "warn");
    } finally {
      button.disabled = false;
    }
  }

  async function deleteKey(id, button) {
    if (!id) return;

    const card = button?.closest("article, .key-card, .key-row, .registered-key");
    const gameVersion = card?.dataset?.keyVersion || "";

    let confirmMessage = "Delete this V2 key profile from this website account?";
    if (gameVersion === "pc_v2") {
      confirmMessage += "\n\nPC V2 serial/key is forever tied to your local character saves. Please be sure you have your keys backed up before removing this key from your profile.";
    }

    if (!window.confirm(confirmMessage)) return;

    button.disabled = true;
    setMessage("Deleting key profile...", "pending");

    try {
      const res = await fetch(`/api/keys/${encodeURIComponent(id)}`, {
        method: "DELETE",
        credentials: "same-origin",
        headers: { "Accept": "application/json" },
      });

      const data = await readJson(res);
      if (!res.ok) {
        throw new Error(data.error || `Delete failed: ${res.status}`);
      }

      setMessage(`Deleted. Sync status: ${data.sync_status}.`, "ok");
      await loadKeys();
    } catch (err) {
      setMessage(err.message || String(err), "warn");
      button.disabled = false;
    }
  }

  function bind() {
    if (bound) return true;

    const form = formEl();
    const list = listEl();
    if (!form || !list) return false;

    setupForm(form);

    const button = qs("button", form);
    if (button) {
      button.addEventListener("click", registerKey);
    }

    form.addEventListener("submit", (ev) => {
      ev.preventDefault();
      registerKey();
    });

    list.addEventListener("click", (ev) => {
      const revealButton = ev.target.closest(".key-reveal-button");
      if (revealButton) {
        revealAccessKey(revealButton.dataset.keyId, revealButton);
        return;
      }

      const button = ev.target.closest(".key-delete-button");
      if (!button) return;
      deleteKey(button.dataset.keyId, button);
    });

    bound = true;

    loadKeys().catch(err => {
      list.innerHTML = `
        <article class="key-row key-row--empty" role="listitem">
          <div>
            <h3>Could not load keys</h3>
            <p>${esc(err.message || err)}</p>
          </div>
        </article>
      `;
    });

    return true;
  }

  function start() {
    [0, 500, 1500, 3000].forEach(ms => {
      window.setTimeout(bind, ms);
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", start);
  } else {
    start();
  }
})();
