/* Universal binder for the 6 onboarding pages.
 *
 * Injected by src/onboarding_v2.py:_load_html_with_binder into every
 * onboarding-*.html. Routes data-action clicks to Python bridge calls,
 * handles the model-progress + permission-polling live updates, and
 * manages the account-form state machine (email → code → success).
 *
 * Action map (handled here):
 *   data-action="setup"          → advance welcome → model
 *   data-action="open-settings"  → open System Settings deep-link for that row's perm
 *   data-action="skip"           → permissions: jump to account (Continue without granting)
 *   data-action="send-code"      → request magic-link, swap form to "code" state
 *   data-action="activate"       → exchange code, on success advance to tour
 *   data-action="resend"         → re-send code with previously typed email
 *   data-action="back-email"     → swap form back to "email" state
 *   data-action="skip-free"      → account: jump to tour (use Free)
 *   data-action="ready"          → advance tour → done
 *   data-action="open-lumo"      → finish (Python writes the marker + closes window)
 *
 * The Continue button on the permissions page is intentionally inside
 * an HTML comment in the designer's markup — this binder injects it
 * (and removes it) based on the live perm-state poll.
 */

(function () {
  // Map designer's data-perm names → Python permissions_wizard slugs.
  // Both naming conventions used valid words, but the bridge expects
  // the Python slug; translate at the boundary so the HTML stays clean.
  var PERM_HTML_TO_SLUG = {
    "microphone": "mic",
    "accessibility": "accessibility",
    "input-monitoring": "input_monitoring",
    "screen-recording": "screen_recording",
  };

  function call(fn, args) {
    if (!window.lumo || !window.lumo.call) {
      console.warn("[onb] bridge missing — running standalone preview?");
      return Promise.resolve({ ok: false });
    }
    return window.lumo.call(fn, args || {});
  }

  // ── Action delegation ───────────────────────────────────────────────

  document.addEventListener("click", function (e) {
    var btn = e.target.closest && e.target.closest("[data-action]");
    if (!btn) return;
    var action = btn.dataset.action;
    if (!action) return;
    e.preventDefault();
    handleAction(action, btn, e);
  });

  function handleAction(action, btn, evt) {
    switch (action) {
      case "setup":
        call("onbAdvance", { to: "model" });
        return;

      case "open-settings": {
        var row = btn.closest(".lumo-perm-row");
        var htmlName = row ? row.dataset.perm : "";
        var slug = PERM_HTML_TO_SLUG[htmlName] || "";
        if (slug) call("onbOpenSettings", { slug: slug });
        return;
      }

      case "skip":  // permissions page → skip without grant
        // CR (2026-07-01) — new flow order:
        //   permissions → mode → account → done
        call("onbAdvance", { to: "mode" });
        return;

      case "continue":  // injected Continue button (after all-granted)
        call("onbAdvance", { to: "mode" });
        return;

      // ── mode chooser (v0.1.21) ────────────────────────────────────
      case "pick-mode": {
        var mode = btn.dataset.mode || "cloud";
        selectModeCard(mode);
        return;
      }
      case "mode-skip":
        // Treat skip as cloud — safer default: no download blocks
        // the user, everything works out of the box.
        call("onbSetMode", { mode: "cloud" }).then(function () {
          call("onbAdvance", { to: "account" });
        });
        return;
      case "mode-continue": {
        var selected = document.querySelector(".mode-card.selected");
        var mode = selected ? selected.dataset.mode : "cloud";
        btn.disabled = true;
        btn.textContent = mode === "local" ? "Downloading…" : "Saving…";
        call("onbSetMode", { mode: mode }).then(function (res) {
          if (!res || !res.ok) {
            btn.disabled = false;
            btn.textContent = "Continue";
            flashMsg((res && res.error) || "Couldn't save mode");
            return;
          }
          if (mode === "local" && res.downloading) {
            // Wait for the model-progress push chain to hit 100 then
            // advance. The push handler below flips a global flag.
            waitForModelReadyThen(function () {
              call("onbAdvance", { to: "account" });
            });
          } else {
            call("onbAdvance", { to: "account" });
          }
        });
        return;
      }

      case "send-code": {
        var emailEl = document.querySelector('input[type="email"], #onb-email');
        var email = emailEl ? emailEl.value.trim() : "";
        if (!email) { flashMsg("Введи email."); return; }
        btn.disabled = true;
        var originalLabel = btn.textContent;
        btn.textContent = "Sending…";
        call("onbSendCode", { email: email }).then(function (res) {
          btn.disabled = false;
          btn.textContent = originalLabel;
          if (res && res.ok) {
            swapAccountState("code", email);
          } else {
            flashMsg((res && (res.message || res.error)) || "Не отправилось");
          }
        }).catch(function (err) {
          btn.disabled = false;
          btn.textContent = originalLabel;
          flashMsg("Ошибка: " + err);
        });
        return;
      }

      case "resend": {
        // Re-send to whatever email is in the "we sent to" line.
        var pendingEmailEl = document.querySelector("[data-pending-email]");
        var email = pendingEmailEl ? pendingEmailEl.textContent.trim() : "";
        if (!email) { swapAccountState("email", ""); return; }
        call("onbSendCode", { email: email }).then(function (res) {
          flashMsg(res && res.ok ? "Sent again" :
                   ((res && (res.message || res.error)) || "Не отправилось"));
        });
        return;
      }

      case "back-email":
        swapAccountState("email", "");
        return;

      case "activate": {
        // Fallback path — auto-submit handles this now, but if someone
        // somehow exposes the button or clicks it programmatically, route
        // through the same code path.
        var code = readOTP();
        if (!code || code.length !== 6) {
          flashMsg("Введи все 6 символов кода.");
          return;
        }
        submitActivation(code);
        return;
      }

      case "skip-free":   // account → use Free, jump to tour
        call("onbAdvance", { to: "tour" });
        return;

      case "ready":       // tour → done
        call("onbAdvance", { to: "done" });
        return;

      case "open-lumo":   // done → close + show bank
        call("onbFinish");
        return;

      default:
        console.warn("[onb] unhandled action:", action);
    }
  }

  // ── Account form state machine ──────────────────────────────────────

  function swapAccountState(state, email) {
    var root = document.querySelector(".lumo-window[data-state]");
    if (root) root.dataset.state = state;
    if (email) {
      document.querySelectorAll("[data-email], [data-pending-email]").forEach(
        function (n) { n.textContent = email; });
    }
    if (state === "code") {
      buildOTPCells();
      makeLinksVisible();
      // Hide the explicit Activate button — auto-submit handles
      // verification as soon as 6 chars land in the cells.
      var activateBtn = document.querySelector('[data-action="activate"]');
      if (activateBtn) activateBtn.style.display = "none";
      setTimeout(function () {
        var c0 = document.querySelector(".lumo-otp-cells input");
        if (c0) c0.focus();
      }, 60);
    }
  }

  function buildOTPCells() {
    var holder = document.querySelector(".lumo-otp-cells");
    if (!holder || holder.dataset.editableReady === "1") return;
    holder.innerHTML = "";
    for (var i = 0; i < 6; i++) {
      var cell = document.createElement("input");
      cell.className = "otp";
      // No maxLength here — the paste handler relies on receiving the
      // full text. Trimming to 1 char is done in code after we read it.
      cell.autocomplete = "off";
      cell.setAttribute("inputmode", "text");
      cell.style.textAlign = "center";
      cell.style.background = "transparent";
      cell.style.outline = "none";
      holder.appendChild(cell);
    }
    holder.dataset.editableReady = "1";
  }

  function makeLinksVisible() {
    // Designer's `.onb-link` is ink-dim by default → reads as plain
    // text. Override to gold on this view so Resend / Back / Skip
    // actually look clickable. (Surgical inline style — keeps the
    // designer's CSS untouched.)
    document.querySelectorAll(".onb-link").forEach(function (a) {
      a.style.color = "var(--accent, #ebb638)";
      a.style.textDecoration = "underline";
      a.style.textUnderlineOffset = "3px";
    });
  }

  function readOTP() {
    var cells = document.querySelectorAll(".lumo-otp-cells input");
    var s = "";
    cells.forEach(function (c) { s += (c.value || ""); });
    return s.toUpperCase();
  }

  // OTP cells: paste handler (Cmd+V) — distributes pasted text across cells.
  document.addEventListener("paste", function (e) {
    if (!e.target.matches || !e.target.matches(".lumo-otp-cells input")) return;
    e.preventDefault();
    var text = (e.clipboardData || window.clipboardData).getData("text") || "";
    text = text.toUpperCase().replace(/[^A-Z0-9]/g, "");
    if (!text) return;
    var cells = Array.from(document.querySelectorAll(".lumo-otp-cells input"));
    var idx = cells.indexOf(e.target);
    if (idx < 0) idx = 0;
    var chars = text.split("");
    for (var i = 0; i < chars.length && idx + i < cells.length; i++) {
      cells[idx + i].value = chars[i];
    }
    var lastFilled = Math.min(idx + chars.length, cells.length - 1);
    if (cells[lastFilled]) cells[lastFilled].focus();
    maybeAutoSubmit();
  });

  // Single-char typing: advance focus, normalize to uppercase, auto-submit.
  document.addEventListener("input", function (e) {
    if (!e.target.matches || !e.target.matches(".lumo-otp-cells input")) return;
    var v = (e.target.value || "").toUpperCase().replace(/[^A-Z0-9]/g, "");
    if (v.length > 1) {
      // Treat as paste fallback (some browsers fire input not paste).
      var cells = Array.from(document.querySelectorAll(".lumo-otp-cells input"));
      var idx = cells.indexOf(e.target);
      var chars = v.split("");
      for (var i = 0; i < chars.length && idx + i < cells.length; i++) {
        cells[idx + i].value = chars[i];
      }
      var lastFilled = Math.min(idx + chars.length, cells.length - 1);
      if (cells[lastFilled]) cells[lastFilled].focus();
      maybeAutoSubmit();
      return;
    }
    e.target.value = v;
    if (v) {
      var n = e.target.nextElementSibling;
      while (n && (!n.matches || !n.matches("input"))) n = n.nextElementSibling;
      if (n) n.focus();
      maybeAutoSubmit();
    }
  });

  document.addEventListener("keydown", function (e) {
    if (!e.target.matches || !e.target.matches(".lumo-otp-cells input")) return;
    if (e.key === "Backspace" && !e.target.value) {
      var p = e.target.previousElementSibling;
      while (p && (!p.matches || !p.matches("input"))) p = p.previousElementSibling;
      if (p) { p.focus(); p.value = ""; }
    }
  });

  // ── Auto-submit when all 6 cells are filled ─────────────────────────

  var _autoSubmitInFlight = false;

  function maybeAutoSubmit() {
    if (_autoSubmitInFlight) return;
    var cells = document.querySelectorAll(".lumo-otp-cells input");
    if (cells.length !== 6) return;
    var code = "";
    cells.forEach(function (c) { code += (c.value || ""); });
    if (code.length !== 6) return;
    submitActivation(code);
  }

  function submitActivation(code) {
    _autoSubmitInFlight = true;
    var holder = document.querySelector(".lumo-otp-cells");
    var pendingEmailEl = document.querySelector("[data-email], [data-pending-email]");
    var email = pendingEmailEl ? pendingEmailEl.textContent.trim() : "";
    call("onbActivate", { email: email, code: code }).then(function (res) {
      _autoSubmitInFlight = false;
      if (res && res.ok) {
        // Success → swap to "success" state, designer's tick animation runs
        // via the existing CSS for .when-success. Auto-advance to tour.
        swapAccountState("success", email);
        setTimeout(function () { call("onbAdvance", { to: "tour" }); }, 1300);
      } else {
        // Shake animation + flash error + clear cells.
        if (holder) {
          holder.style.animation = "lumo-shake 360ms cubic-bezier(.36,.07,.19,.97)";
          setTimeout(function () { holder.style.animation = ""; }, 400);
        }
        flashMsg((res && (res.message || res.error)) || "Неверный или истёкший код");
        var firstCell = document.querySelector(".lumo-otp-cells input");
        document.querySelectorAll(".lumo-otp-cells input").forEach(function (c) {
          c.value = "";
        });
        if (firstCell) firstCell.focus();
      }
    }).catch(function (err) {
      _autoSubmitInFlight = false;
      flashMsg("Ошибка: " + err);
    });
  }

  // Inject shake keyframes once (idempotent).
  (function ensureShakeCSS() {
    if (document.getElementById("lumo-shake-css")) return;
    var style = document.createElement("style");
    style.id = "lumo-shake-css";
    style.textContent =
      "@keyframes lumo-shake {"
      + "10%,90% { transform: translateX(-2px); }"
      + "20%,80% { transform: translateX(4px); }"
      + "30%,50%,70% { transform: translateX(-8px); }"
      + "40%,60% { transform: translateX(8px); }"
      + "}";
    document.head.appendChild(style);
  })();

  // ── Permissions page: live polling ──────────────────────────────────

  function startPermissionsPolling() {
    if (!document.querySelector(".lumo-perm-row")) return;
    var foot = document.querySelector(".onb-foot");
    var slugByHtml = PERM_HTML_TO_SLUG;
    // Reverse map: slug → HTML perm name
    var htmlBySlug = {};
    Object.keys(slugByHtml).forEach(function (h) { htmlBySlug[slugByHtml[h]] = h; });

    function tick() {
      call("onbPermissionsState").then(function (res) {
        var perms = (res && res.perms) || [];
        var allRequired = true;
        perms.forEach(function (p) {
          var html = htmlBySlug[p.slug] || "";
          if (!html) return;
          var row = document.querySelector('.lumo-perm-row[data-perm="' + html + '"]');
          if (!row) return;
          row.dataset.status = p.granted ? "granted" : "pending";
          row.classList.toggle("is-granted", !!p.granted);
          row.classList.toggle("is-denied", false);  // we don't distinguish denied vs pending
          var dot = row.querySelector(".pr-dot");
          if (dot) {
            dot.classList.remove("granted", "pending", "denied");
            dot.classList.add(p.granted ? "granted" : "pending");
            dot.title = p.granted ? "Granted" : "Pending";
          }
          // Swap action area: Granted tag vs Open Settings button.
          var action = row.querySelector(".pr-action");
          if (action) {
            if (p.granted) {
              action.innerHTML =
                '<span class="pr-granted-tag">'
                + '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" '
                + 'aria-hidden="true"><path d="M5 12.5l4.2 4.2L19 7" '
                + 'stroke="currentColor" stroke-width="2.4" stroke-linecap="round" '
                + 'stroke-linejoin="round"/></svg> Granted</span>';
            } else if (!action.querySelector('[data-action="open-settings"]')) {
              action.innerHTML =
                '<button class="btn btn-ghost btn-sm" data-action="open-settings">'
                + "Open Settings</button>";
            }
          }
          if (p.required && !p.granted) allRequired = false;
        });
        // Show Continue button when all required are green.
        if (foot) {
          var existing = foot.querySelector('[data-foot="continue"]');
          if (allRequired && !existing) {
            var btn = document.createElement("button");
            btn.className = "btn btn-primary";
            btn.setAttribute("data-foot", "continue");
            btn.setAttribute("data-action", "continue");
            btn.textContent = "Continue";
            // Insert before the skip link / spacer if present.
            var firstChild = foot.firstChild;
            foot.insertBefore(btn, firstChild);
          } else if (!allRequired && existing) {
            existing.parentNode.removeChild(existing);
          }
        }
      }).catch(function (err) {
        console.warn("[onb] perms poll failed:", err);
      });
    }
    tick();
    var iv = setInterval(tick, 1500);
    window.addEventListener("beforeunload", function () { clearInterval(iv); });
  }

  // ── Model page: progress subscription ───────────────────────────────

  function startModelDownload() {
    var bar = document.querySelector(".lumo-progress-bar");
    if (!bar) return;
    var fill = bar.querySelector(".pb-fill") || bar.firstElementChild;
    var statusEl = document.querySelector(".progress-status");
    var metaEl   = document.querySelector(".progress-meta");

    function applyProgress(payload) {
      var p = (payload && payload.percent) || 0;
      var s = (payload && payload.status)  || "";
      if (fill && fill.style) fill.style.width = Math.max(0, Math.min(100, p)) + "%";
      bar.setAttribute("aria-valuenow", String(Math.round(p)));
      if (statusEl) statusEl.textContent = s;
      if (metaEl) {
        // Designer's layout: meta is "filename · percent · throughput".
        // Keep first/last segments; replace middle with percent.
        metaEl.textContent = Math.round(p) + "%";
      }
    }
    if (window.lumo && window.lumo.on) {
      window.lumo.on("model-progress", applyProgress);
    }
    // Kick off the download from Python.
    call("onbStartModel");
  }

  // ── CR (2026-07-01) — mode chooser bootstrap ────────────────────────

  var MODE_READY_LISTENERS = [];

  function selectModeCard(mode) {
    document.querySelectorAll(".mode-card").forEach(function (c) {
      c.classList.toggle("selected", c.dataset.mode === mode);
    });
    var cont = document.querySelector('[data-action="mode-continue"]');
    if (cont) cont.disabled = false;
    var panel = document.querySelector("[data-dl-panel]");
    if (panel) panel.style.display = (mode === "local") ? "" : "none";
  }

  function waitForModelReadyThen(cb) {
    MODE_READY_LISTENERS.push(cb);
  }

  function fmtBytes(n) {
    n = Number(n || 0);
    if (n <= 0) return "—";
    var mb = n / 1048576;
    if (mb < 1024) return mb.toFixed(0) + " MB";
    return (mb / 1024).toFixed(2) + " GB";
  }

  function startModeChooser() {
    var bar    = document.querySelector("[data-dl-bar]");
    var status = document.querySelector("[data-dl-status]");
    var msg    = document.querySelector("[data-dl-msg]");
    var bytes  = document.querySelector("[data-dl-bytes]");
    if (!bar || !window.lumo || !window.lumo.on) return;
    window.lumo.on("model-progress", function (payload) {
      var p = (payload && payload.percent) || 0;
      var s = (payload && payload.status)  || "";
      bar.style.width = Math.max(0, Math.min(100, p)) + "%";
      if (status) status.textContent = s || (p >= 100 ? "Model ready" : "Downloading…");
      if (msg && payload && payload.msg) msg.textContent = payload.msg;
      if (bytes && payload && (payload.bytes_done || payload.bytes_total)) {
        bytes.textContent = fmtBytes(payload.bytes_done)
                          + " / " + fmtBytes(payload.bytes_total);
      }
      if (p >= 100) {
        var listeners = MODE_READY_LISTENERS.splice(0);
        listeners.forEach(function (cb) { try { cb(); } catch (e) {} });
      }
    });
  }

  // ── Per-page bootstrap ──────────────────────────────────────────────

  function init() {
    // Decide what page we're on by looking for a marker class / element.
    if (document.querySelector(".lumo-perm-row")) {
      startPermissionsPolling();
    }
    if (document.querySelector(".lumo-progress-bar")) {
      startModelDownload();
    }
    if (document.querySelector(".mode-grid")) {
      startModeChooser();
    }
  }

  // ── Helpers ────────────────────────────────────────────────────────

  function flashMsg(text) {
    var box = document.createElement("div");
    box.style.cssText =
      "position:fixed;bottom:24px;left:50%;transform:translateX(-50%);"
      + "background:#141414;border:1px solid rgba(255,255,255,.1);"
      + "color:#efeae4;padding:10px 18px;border-radius:10px;"
      + "font-family:monospace;font-size:12px;z-index:9999;"
      + "box-shadow:0 12px 32px rgba(0,0,0,.6);";
    box.textContent = text;
    document.body.appendChild(box);
    setTimeout(function () { box.style.opacity = "0";
                             box.style.transition = "opacity 300ms"; }, 2200);
    setTimeout(function () { box.parentNode && box.parentNode.removeChild(box); }, 2700);
  }

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