import { mountWespLogoLoader } from "/static/js/wesp-logo-loader.js";
import { createSetupOrchestrator } from "/static/js/setup-orchestrator.js";
import { SetupTouchKeyboard } from "/static/js/setup-touch-keyboard.js";
import {
  DEFAULT_PASSWORD_MIN_LEN,
  validateNewUserCredentials,
} from "/static/js/wesp-user-credentials.js";

const API = {
  status: "/api/setup/status",
  deviceRole: "/api/setup/device-role",
  network: "/api/setup/network",
  sync: "/api/setup/sync",
  syncProgress: "/api/setup/sync/progress",
  users: "/api/setup/users",
  hardware: "/api/setup/hardware/check",
  kioskPrepare: "/api/setup/kiosk/prepare",
  kioskSetupAvailable: "/api/kiosk/setup-available",
  complete: "/api/setup/complete",
  kioskAccessLink: "/api/kiosk/access-link",
  kioskStatus: "/api/kiosk/status",
};

const THEME_STORAGE_KEY = "theme";
const STEP_LABELS = {
  theme: "Тема",
  welcome: "Начало",
  device: "Устройство",
  network: "Сеть",
  sync: "Сервер",
  users: "Пользователи",
  hardware: "Весы",
  kiosk: "Терминал",
  bootstrap: "Синхронизация",
  done: "Готово",
};
function passwordMinLen(statusData) {
  const n = Number(statusData?.user_rules?.password_min_len);
  return Number.isFinite(n) && n > 0 ? n : DEFAULT_PASSWORD_MIN_LEN;
}

async function fetchJson(url, options) {
  const response = await fetch(url, options);
  const data = await response.json().catch(() => ({}));
  if (!response.ok) {
    const msg = globalThis.WespUserMessages?.messageFromResponseBody?.(
      data,
      "Не удалось выполнить запрос"
    ) || "Не удалось выполнить запрос";
    throw new Error(msg);
  }
  return data;
}

function setupUserError(message, fallback) {
  return globalThis.WespUserMessages?.userFacingMessage?.(message, fallback) || fallback || "Не удалось выполнить операцию.";
}

async function canIssueKioskAccessLink() {
  try {
    const data = await fetchJson(API.kioskSetupAvailable);
    return !!data.can_issue_access_link;
  } catch {
    return false;
  }
}

function el(tag, className, text) {
  const node = document.createElement(tag);
  if (className) node.className = className;
  if (text != null) node.textContent = text;
  return node;
}

function field(label, input) {
  const wrap = el("div", "wesp-setup__field");
  wrap.appendChild(el("label", "wesp-setup__label", label));
  wrap.appendChild(input);
  return wrap;
}

function fieldWithSuffix(label, input, suffixText) {
  const wrap = el("div", "wesp-setup__field");
  wrap.appendChild(el("label", "wesp-setup__label", label));
  const group = el("div", "wesp-setup__input-group");
  group.appendChild(input);
  group.appendChild(el("span", "wesp-setup__input-suffix", suffixText));
  wrap.appendChild(group);
  return wrap;
}

function normalizeHostnameLabel(value) {
  return String(value || "").trim().toLowerCase().replace(/\.local$/i, "");
}

function formatSyncServerDisplay(serverUrl) {
  const raw = String(serverUrl || "").trim().replace(/\/+$/, "");
  if (raw) return raw;
  return "http://komton_srv_1.local";
}

function buildSyncServerUrl(raw) {
  const value = String(raw || "").trim().replace(/\/+$/, "");
  if (!value) return "";
  if (/^https?:\/\//i.test(value)) return value;
  if (/^\d+\.\d+\.\d+\.\d+(:\d+)?$/i.test(value)) return `http://${value}`;
  const host = normalizeHostnameLabel(value);
  return host ? `http://${host}.local` : "";
}

function readSetupTheme() {
  try {
    const t = (localStorage.getItem(THEME_STORAGE_KEY) || "").trim().toLowerCase();
    return t === "light" ? "light" : "dark";
  } catch {
    return "dark";
  }
}

function applySetupTheme(theme) {
  const next = theme === "dark" ? "dark" : "light";
  document.documentElement.setAttribute("data-theme", next);
  try {
    localStorage.setItem(THEME_STORAGE_KEY, next);
  } catch {
    /* ignore */
  }
}

function stepIndicatorLabel(step) {
  return STEP_LABELS[step] || step || "";
}

function textInput(id, placeholder, type = "text") {
  const input = document.createElement("input");
  input.className = "wesp-setup__input";
  input.id = id;
  input.type = type;
  input.placeholder = placeholder;
  return input;
}

function postSetupLandingPath(statusData) {
  const role = statusData?.setup?.device_role;
  const scaleHw = Boolean(statusData?.hardware?.scale_hardware_platform);
  if (role === "client" && scaleHw) return "/scales";
  return "/login";
}

export async function mountSetupWizard(root) {
  const orchestrator = createSetupOrchestrator();
  let statusData = null;
  let bootstrapTimer = 0;
  let activeTouchKeyboard = null;
  let introStarted = false;

  const intro = el("div", "wesp-setup__intro");
  intro.setAttribute("aria-hidden", "false");
  root.appendChild(intro);

  const panel = el("div", "wesp-setup__panel wesp-setup__panel--hidden");
  panel.setAttribute("role", "dialog");
  panel.setAttribute("aria-live", "polite");
  root.appendChild(panel);

  const statusPromise = loadStatusEarly();

  async function loadStatusEarly() {
    statusData = await fetchJson(API.status);
    if (statusData.setup?.setup_completed) {
      window.location.href = postSetupLandingPath(statusData);
      return statusData;
    }
    if (statusData.setup?.device_role === "client") {
      orchestrator.setDeviceRole("client");
    } else {
      orchestrator.setDeviceRole("server");
    }
    return statusData;
  }

  async function playIntroAnimation() {
    if (introStarted) return;
    introStarted = true;
    await mountWespLogoLoader(intro, {
      loop: false,
      embedded: true,
      transparentBackground: true,
      knockoutBackground: true,
      darkTheme: readSetupTheme() === "dark",
      hideChrome: true,
      onComplete: () => revealWelcome(),
    });
  }

  async function revealWelcome() {
    intro.classList.add("wesp-setup__intro--done");
    intro.setAttribute("aria-hidden", "true");
    await statusPromise;
    if (statusData?.setup?.setup_completed) return;
    panel.classList.remove("wesp-setup__panel--hidden");
    renderTheme();
    window.setTimeout(() => {
      if (intro.isConnected) intro.remove();
    }, 480);
  }

  playIntroAnimation();

  function setPanelBusy(busy, label = "Сохранение…") {
    let overlay = panel.querySelector(".wesp-setup__busy");
    if (busy) {
      if (!overlay) {
        overlay = el("div", "wesp-setup__busy");
        overlay.appendChild(el("span", "wesp-setup__spinner"));
        overlay.appendChild(el("span", "wesp-setup__busy-label", label));
        panel.appendChild(overlay);
      } else {
        overlay.querySelector(".wesp-setup__busy-label").textContent = label;
        overlay.hidden = false;
      }
      panel.classList.add("wesp-setup__panel--busy");
      panel.setAttribute("aria-busy", "true");
      return;
    }
    overlay?.remove();
    panel.classList.remove("wesp-setup__panel--busy");
    panel.removeAttribute("aria-busy");
  }

  async function withStepLoading(label, fn) {
    setPanelBusy(true, label);
    try {
      return await fn();
    } finally {
      setPanelBusy(false);
    }
  }

  function renderShell(title, lead) {
    activeTouchKeyboard?.destroy();
    activeTouchKeyboard = null;
    panel.replaceChildren();
    panel.classList.remove("wesp-setup__panel--busy");
    panel.removeAttribute("aria-busy");

    const header = el("div", "wesp-setup__header");
    header.appendChild(
      el("div", "wesp-setup__step-indicator", `Шаг · ${stepIndicatorLabel(orchestrator.currentStep)}`),
    );
    header.appendChild(el("h1", "wesp-setup__title", title));
    if (lead) header.appendChild(el("p", "wesp-setup__lead", lead));
    panel.appendChild(header);

    const content = el("div", "wesp-setup__content");
    panel.appendChild(content);
    return content;
  }

  function populateActionRow(row, ...buttons) {
    row.replaceChildren();
    let primaryIndex = -1;
    buttons.forEach((b, i) => {
      if (b.classList.contains("wesp-setup__btn--primary")) primaryIndex = i;
    });
    buttons.forEach((b, i) => {
      if (!b.classList.contains("wesp-setup__btn--primary")) {
        b.classList.add("wesp-setup__btn--ghost");
      }
      if (i === primaryIndex && primaryIndex > 0) {
        row.appendChild(el("div", "wesp-setup__actions-fill"));
      }
      row.appendChild(b);
    });
  }

  function actions(...buttons) {
    const footer = el("div", "wesp-setup__footer");
    const row = el("div", "wesp-setup__actions");
    populateActionRow(row, ...buttons);
    footer.appendChild(row);
    panel.appendChild(footer);
    return row;
  }

  function msg(text, kind, target) {
    const m = el("p", `wesp-setup__msg${kind ? ` wesp-setup__msg--${kind}` : ""}`, text || "");
    (target || panel.querySelector(".wesp-setup__content") || panel).appendChild(m);
    return m;
  }

  function btn(label, className, onClick) {
    const b = el("button", `wesp-setup__btn${className ? ` ${className}` : ""}`, label);
    b.type = "button";
    b.addEventListener("click", onClick);
    return b;
  }

  async function loadStatus() {
    statusData = await fetchJson(API.status);
    if (statusData.setup?.setup_completed) {
      window.location.href = postSetupLandingPath(statusData);
      return statusData;
    }
    if (statusData.setup?.device_role === "client") {
      orchestrator.setDeviceRole("client");
    } else {
      orchestrator.setDeviceRole("server");
    }
    return statusData;
  }

  function shouldShowBootstrap() {
    return !!statusData?.setup?.sync_server_connected;
  }

  function goAfterKiosk() {
    if (shouldShowBootstrap()) renderBootstrap();
    else goComplete();
  }

  async function goComplete() {
    orchestrator.setStep("done");
    const data = await withStepLoading("Завершение…", () =>
      fetchJson(API.complete, { method: "POST" }),
    );
    const fallbackRedirect = postSetupLandingPath(statusData);
    const redirect = data.redirect || fallbackRedirect;
    const opensScales = redirect === "/scales";
    renderShell(
      "Готово",
      opensScales
        ? "Система настроена. Сейчас откроется экран весов."
        : "Система настроена. Сейчас откроется страница входа.",
    );
    msg(data.message, "ok");
    actions(btn("Открыть", "wesp-setup__btn--primary", () => {
      window.location.href = redirect;
    }));
    window.setTimeout(() => {
      window.location.href = redirect;
    }, 2200);
  }

  function renderTheme() {
    orchestrator.setStep("theme");
    const content = renderShell(
      "Оформление",
      "Выберите тему. Её можно сменить позже в меню терминала.",
    );
    let selected = readSetupTheme();
    applySetupTheme(selected);

    const grid = el("div", "wesp-setup__role-grid wesp-setup__role-grid--cards wesp-setup__theme-grid");
    const options = [
      ["light", "Светлая", "Светлый фон и контрастные элементы."],
      ["dark", "Тёмная", "Удобнее при слабом освещении."],
    ];
    const buttons = [];
    options.forEach(([value, title, hint]) => {
      const b = el("button", `wesp-setup__role-btn wesp-setup__role-card wesp-setup__theme-card wesp-setup__theme-card--${value}`, "");
      b.type = "button";
      b.innerHTML = `<strong>${title}</strong><span>${hint}</span>`;
      if (selected === value) b.classList.add("is-selected");
      b.addEventListener("click", () => {
        selected = value;
        applySetupTheme(selected);
        buttons.forEach((x) => x.classList.toggle("is-selected", x === b));
      });
      buttons.push(b);
      grid.appendChild(b);
    });
    content.appendChild(grid);

    actions(btn("Далее", "wesp-setup__btn--primary", () => renderWelcome()));
  }

  function renderWelcome() {
    orchestrator.setStep("welcome");
    renderShell(
      "Добро пожаловать",
      "Мастер поможет выбрать роль устройства и базовые параметры.",
    );
    const health = statusData?.health;
    if (health?.ok) {
      msg("Система готова к настройке.", "ok");
    } else {
      msg("Проверка health… при ошибках миграций обратитесь к установщику.", "error");
    }
    actions(btn("Начать", "wesp-setup__btn--primary", () => renderDevice()));
  }

  function renderDevice() {
    orchestrator.setStep("device");
    const content = renderShell("Тип устройства", "Где будет работать это устройство?");
    const grid = el("div", "wesp-setup__role-grid wesp-setup__role-grid--cards");
    const roles = [
      ["server", "Главный сервер"],
      ["client", "Весовой терминал"],
    ];
    let selected = orchestrator.deviceRole === "client" ? "client" : "server";
    const buttons = [];
    roles.forEach(([value, title]) => {
      const b = el("button", "wesp-setup__role-btn wesp-setup__role-card", "");
      b.type = "button";
      b.innerHTML = `<strong>${title}</strong>`;
      if (selected === value) b.classList.add("is-selected");
      b.addEventListener("click", () => {
        selected = value;
        buttons.forEach((x) => x.classList.toggle("is-selected", x === b));
      });
      buttons.push(b);
      grid.appendChild(b);
    });
    content.appendChild(grid);
    const statusEl = msg("");
    actions(
      btn("Назад", "", () => renderWelcome()),
      btn("Далее", "wesp-setup__btn--primary", async () => {
        try {
          const data = await withStepLoading("Сохранение…", () =>
            fetchJson(API.deviceRole, {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify({ device_role: selected }),
            }),
          );
          if (data?.connection && statusData) {
            statusData.connection = data.connection;
          }
          orchestrator.setDeviceRole(selected);
          if (selected === "server") renderNetwork();
          else renderSync();
        } catch (e) {
          statusEl.textContent = setupUserError(e.message, "Не удалось выполнить шаг настройки.");
          statusEl.className = "wesp-setup__msg wesp-setup__msg--error";
        }
      }),
    );
  }

  function renderNetwork() {
    orchestrator.setStep("network");
    const content = renderShell(
      "Сеть",
      "Как этот сервер будет называться в вашей Wi‑Fi сети.",
    );
    const net = statusData?.network || {};
    const hostDefault = normalizeHostnameLabel(
      net.local_hostname || net.default_local_hostname || "komton_srv_1",
    );
    const hostLocked = !!net.local_hostname_locked_by_env;
    const hostInput = textInput("setupHost", hostDefault);
    hostInput.placeholder = "komton_srv_1";
    hostInput.disabled = hostLocked;
    hostInput.autocomplete = "off";
    hostInput.spellcheck = false;
    hostInput.addEventListener("input", () => {
      const cleaned = normalizeHostnameLabel(hostInput.value);
      if (hostInput.value !== cleaned) hostInput.value = cleaned;
    });

    content.appendChild(fieldWithSuffix("Имя сервера", hostInput, ".local"));
    content.appendChild(
      el(
        "p",
        "wesp-setup__hint",
        "Латиница и цифры, без пробелов. Суффикс .local добавится автоматически.",
      ),
    );
    if (net.detected?.lan_ip) {
      content.appendChild(el("p", "wesp-setup__hint", `IP в сети: ${net.detected.lan_ip}`));
    }
    const statusEl = msg("");
    actions(
      btn("Назад", "", () => renderDevice()),
      btn("Далее", "wesp-setup__btn--primary", async () => {
        try {
          const body = { local_hostname: normalizeHostnameLabel(hostInput.value) };
          if (!net.mdns_enabled_locked_by_env) {
            body.mdns_enabled = true;
          }
          await withStepLoading("Сохранение…", async () => {
            await fetchJson(API.network, {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify(body),
            });
            await loadStatus();
          });
          renderUsers();
        } catch (e) {
          statusEl.textContent = setupUserError(e.message, "Не удалось выполнить шаг настройки.");
          statusEl.className = "wesp-setup__msg wesp-setup__msg--error";
        }
      }),
    );
  }

  function renderUsers() {
    orchestrator.setStep("users");
    const content = renderShell(
      "Учётные записи",
      "Создайте учётную запись зоотехника для работы с рецептами.",
    );
    const zootechUsers = statusData?.zootech_users || [];
    if (zootechUsers.length) {
      content.appendChild(
        el(
          "p",
          "wesp-setup__info",
          `Уже есть: ${zootechUsers.join(", ")}. Тот же логин — обновит пароль, новый — добавит ещё одного зоотехника.`,
        ),
      );
    }
    const zootechLogin = textInput("zootechLogin", zootechUsers[0] || "zootech");
    const zootechPass = textInput("zootechPass", "Пароль", "password");
    const zootechConfirm = textInput("zootechConfirm", "Повтор пароля", "password");
    content.appendChild(field("Логин", zootechLogin));
    content.appendChild(field("Пароль", zootechPass));
    content.appendChild(field("Подтверждение", zootechConfirm));

    const statusEl = msg("");
    actions(
      btn("Назад", "", () => renderNetwork()),
      btn("Далее", "wesp-setup__btn--primary", async () => {
        const validationError = validateNewUserCredentials({
          login: zootechLogin.value,
          password: zootechPass.value,
          confirmPassword: zootechConfirm.value,
          passwordMinLen: passwordMinLen(statusData),
          label: "Зоотехник",
        });
        if (validationError) {
          statusEl.textContent = validationError;
          statusEl.className = "wesp-setup__msg wesp-setup__msg--error";
          return;
        }
        try {
          const data = await withStepLoading("Проверка…", () =>
            fetchJson(API.users, {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify({
                zootech: {
                  login: zootechLogin.value.trim(),
                  password: zootechPass.value,
                  confirm: zootechConfirm.value,
                },
              }),
            }),
          );
          await loadStatus();
          statusEl.textContent = data.message || "Сохранено.";
          statusEl.className = "wesp-setup__msg wesp-setup__msg--ok";
          window.setTimeout(() => goComplete(), 350);
        } catch (e) {
          statusEl.textContent = setupUserError(e.message, "Не удалось выполнить шаг настройки.");
          statusEl.className = "wesp-setup__msg wesp-setup__msg--error";
        }
      }),
    );
  }

  function renderHardware() {
    orchestrator.setStep("hardware");
    const content = renderShell(
      "Весы",
      "Проверьте подключение весов или выполните калибровку.",
    );
    const platform = statusData?.hardware || {};
    if (platform.scale_hardware_platform) {
      content.appendChild(
        el(
          "p",
          "wesp-setup__hint",
          "Платформа ARM — можно подключить весы HX711 к Raspberry Pi.",
        ),
      );
    } else {
      content.appendChild(
        el(
          "p",
          "wesp-setup__info wesp-setup__info--warn",
          platform.message || "Весы не найдены",
        ),
      );
    }
    const statusEl = msg("");
    actions(
      btn("Назад", "", () => renderSync()),
      btn("Проверить весы", "", async () => {
        if (!platform.scale_hardware_platform) {
          statusEl.textContent = platform.message || "Весы не найдены";
          statusEl.className = "wesp-setup__msg wesp-setup__msg--error";
          return;
        }
        try {
          const data = await withStepLoading("Проверка весов…", () =>
            fetchJson(API.hardware, { method: "POST" }),
          );
          statusEl.textContent = data.ok
            ? `OK · вес ${data.hardware?.current_weight_kg ?? "—"} кг`
            : "HX711 не отвечает — проверьте подключение или включите симуляцию в админке.";
          statusEl.className = `wesp-setup__msg ${data.ok ? "wesp-setup__msg--ok" : "wesp-setup__msg--error"}`;
        } catch (e) {
          statusEl.textContent = setupUserError(e.message, "Не удалось выполнить шаг настройки.");
          statusEl.className = "wesp-setup__msg wesp-setup__msg--error";
        }
      }),
      btn("Калибровка", "", () => {
        if (window.WespKioskCalibration?.openModal) {
          window.WespKioskCalibration.openModal({ zIndex: 10050, theme: readSetupTheme() });
          return;
        }
        window.open("/calibration?embed=1", "_blank", "noopener,noreferrer");
      }),
      btn("Далее", "wesp-setup__btn--primary", () => renderKiosk()),
    );
  }

  async function renderKiosk() {
    orchestrator.setStep("kiosk");

    const localSetup = await canIssueKioskAccessLink();
    if (!localSetup) {
      const content = renderShell(
        "Настройка терминала",
        "Этот шаг выполняется на самом терминале.",
      );
      content.appendChild(el("p", "wesp-setup__info", "Настройка производится на терминале."));
      actions(
        btn("Назад", "", () => renderHardware()),
        btn("Далее", "wesp-setup__btn--primary", () => goAfterKiosk()),
      );
      return;
    }

    const content = renderShell(
      "Настройка терминала",
      "Привязка планшета тракториста по QR-коду.",
    );
    content.appendChild(
      el(
        "p",
        "wesp-setup__info",
        "Дополнительные планшеты можно привязать позже через меню ☰ → Привязать терминал.",
      ),
    );
    const qrWrap = el("div", "wesp-setup__qr-wrap");
    const qr = el("img", "wesp-setup__qr");
    qr.alt = "QR Start URL";
    const urlEl = el("p", "wesp-setup__url");
    qrWrap.appendChild(qr);
    content.appendChild(qrWrap);
    content.appendChild(urlEl);
    const statusEl = msg("");

    try {
      await withStepLoading("Подготовка QR…", async () => {
        await fetchJson(API.kioskPrepare, { method: "POST" });
        const data = await fetchJson(API.kioskAccessLink);
        qr.src = data.qr_image_url || "";
        urlEl.textContent = data.start_url || "";
      });
      statusEl.textContent = "Вставьте ссылку в Fully Kiosk → Start URL.";
      statusEl.className = "wesp-setup__msg wesp-setup__msg--ok";
    } catch (e) {
      statusEl.textContent = setupUserError(e.message, "Не удалось выполнить шаг настройки.");
      statusEl.className = "wesp-setup__msg wesp-setup__msg--error";
    }

    actions(
      btn("Назад", "", () => renderHardware()),
      btn("Проверить статус", "", async () => {
        try {
          const st = await withStepLoading("Проверка…", () => fetchJson(API.kioskStatus));
          statusEl.textContent = st.paired ? "Терминал привязан." : "Ожидание привязки…";
          statusEl.className = `wesp-setup__msg ${st.paired ? "wesp-setup__msg--ok" : ""}`;
        } catch (e) {
          statusEl.textContent = setupUserError(e.message, "Не удалось выполнить шаг настройки.");
          statusEl.className = "wesp-setup__msg wesp-setup__msg--error";
        }
      }),
      btn("Пропустить", "", () => goAfterKiosk()),
      btn("Далее", "wesp-setup__btn--primary", () => goAfterKiosk()),
    );
  }

  function renderSync() {
    orchestrator.setStep("sync");
    const content = renderShell(
      "Подключение к серверу",
      "Укажите адрес главного сервера и имя этого терминала.",
    );
    const conn = statusData?.connection || {};
    const serverLocked = !!conn.server_url_locked_by_env;
    const serverInput = textInput(
      "syncServer",
      serverLocked ? conn.server_url || conn.env_server_url || "" : formatSyncServerDisplay(conn.server_url),
    );
    serverInput.placeholder = "komton_srv_1.local";
    serverInput.disabled = serverLocked;
    content.appendChild(field("Адрес сервера", serverInput));
    if (serverLocked) {
      content.appendChild(
        el(
          "p",
          "wesp-setup__hint",
          "Адрес задан в настройках системы (WESP_SYNC_SERVER_URL).",
        ),
      );
    } else {
      content.appendChild(
        el(
          "p",
          "wesp-setup__hint",
          "Имя сервера (komton_srv_1.local) или IP, например http://192.168.0.10.",
        ),
      );
    }

    const nameDefault =
      conn.client_name ||
      normalizeHostnameLabel(statusData?.network?.local_hostname || "") ||
      statusData?.network?.detected?.os_hostname ||
      "vesy_1";
    const nameInput = textInput("syncName", nameDefault);
    nameInput.placeholder = "vesy_1";
    content.appendChild(field("Название терминала", nameInput));

    const statusEl = msg("");
    let actionsRow = null;

    async function submitSync(offline) {
      const serverUrl = buildSyncServerUrl(serverInput.value);
      if (!serverUrl && !serverLocked) {
        statusEl.textContent = "Укажите адрес сервера.";
        statusEl.className = "wesp-setup__msg wesp-setup__msg--error";
        return;
      }
      const body = {
        client_name: nameInput.value.trim(),
        offline: !!offline,
      };
      if (!serverLocked) {
        body.server_url = serverUrl;
      }
      await withStepLoading(offline ? "Сохранение…" : "Подключение…", () =>
        fetchJson(API.sync, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(body),
        }),
      );
      await loadStatus();
      renderHardware();
    }

    function showSyncFailureChoices(errorMessage) {
      statusEl.textContent = setupUserError(errorMessage, "Не удалось подключиться к серверу.");
      statusEl.className = "wesp-setup__msg wesp-setup__msg--error";
      if (!actionsRow) return;
      populateActionRow(
        actionsRow,
        btn("Исправить адрес", "", () => {
          serverInput.focus();
          statusEl.textContent = "";
          statusEl.className = "wesp-setup__msg";
          populateActionRow(
            actionsRow,
            btn("Назад", "", () => renderDevice()),
            btn("Далее", "wesp-setup__btn--primary", onNext),
          );
        }),
        btn("Повторить", "wesp-setup__btn--primary", onNext),
        btn("Продолжить без сервера", "", async () => {
          try {
            await submitSync(true);
          } catch (e) {
            statusEl.textContent = setupUserError(e.message, "Не удалось выполнить шаг настройки.");
            statusEl.className = "wesp-setup__msg wesp-setup__msg--error";
          }
        }),
      );
    }

    async function onNext() {
      try {
        await submitSync(false);
      } catch (e) {
        showSyncFailureChoices(e.message);
      }
    }

    actionsRow = actions(
      btn("Назад", "", () => renderDevice()),
      btn("Далее", "wesp-setup__btn--primary", onNext),
    );

    activeTouchKeyboard = new SetupTouchKeyboard(panel);
    if (!serverInput.disabled) {
      activeTouchKeyboard.attach(serverInput, { layout: "url", maxLength: 256 });
    }
    activeTouchKeyboard.attach(nameInput, { layout: "hostname", maxLength: 200 });
    nameInput.addEventListener("input", () => {
      const cleaned = normalizeHostnameLabel(nameInput.value);
      if (nameInput.value !== cleaned) nameInput.value = cleaned;
    });
    window.setTimeout(() => {
      if (!serverInput.disabled) serverInput.focus();
      else nameInput.focus();
    }, 80);
  }

  function renderBootstrap() {
    if (!shouldShowBootstrap()) {
      goComplete();
      return;
    }
    orchestrator.setStep("bootstrap");
    const content = renderShell(
      "Первая синхронизация",
      "Дождитесь загрузки данных с сервера.",
    );
    const bar = el("div", "wesp-setup__progress-bar");
    const fill = el("div", "wesp-setup__progress-fill");
    bar.appendChild(fill);
    content.appendChild(bar);
    const statusEl = msg("Ожидание синхронизации…");

    function stopPoll() {
      if (bootstrapTimer) window.clearInterval(bootstrapTimer);
      bootstrapTimer = 0;
    }

    async function poll() {
      try {
        const data = await fetchJson(API.syncProgress);
        const p = data.initial_sync_progress || {};
        const percent = p.percent != null ? Number(p.percent) : data.first_bootstrap_done ? 100 : 10;
        fill.style.width = `${Math.min(100, percent)}%`;
        orchestrator.setBootstrapProgress(percent / 100);
        if (data.first_bootstrap_done) {
          stopPoll();
          statusEl.textContent = "Синхронизация завершена.";
          statusEl.className = "wesp-setup__msg wesp-setup__msg--ok";
        } else if (p.active) {
          statusEl.textContent = `Синхронизация… ${Math.round(percent)}%`;
        }
      } catch (e) {
        statusEl.textContent = setupUserError(e.message, "Не удалось выполнить шаг настройки.");
        statusEl.className = "wesp-setup__msg wesp-setup__msg--error";
      }
    }

    poll();
    bootstrapTimer = window.setInterval(poll, 2000);

    actions(
      btn("Назад", "", () => {
        stopPoll();
        renderKiosk();
      }),
      btn("Далее", "wesp-setup__btn--primary", () => {
        stopPoll();
        goComplete();
      }),
    );
  }

  await statusPromise;
}

if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", () => {
    const root = document.getElementById("wespSetupRoot");
    if (root) mountSetupWizard(root);
  });
} else {
  const root = document.getElementById("wespSetupRoot");
  if (root) mountSetupWizard(root);
}
