/* ============================================================
   store.jsx — persistence + live merge (dual-mode)
   • Cloud mode   : talks to the Azure Functions API (/api/*), which is
                    backed by Cosmos DB (pages + reports) and Blob Storage
                    (images). This is the default in any real deployment.
   • Offline mode : if the API is unreachable (e.g. opening the static
                    files directly with no backend), it transparently falls
                    back to localStorage so the site still renders/edits.
   Admin writes are gated by a console password (POST /api/login → a signed
   session token). Produces the live NAV / PAGES / PAGE_BY_ID / PAGE_ICON.
   ============================================================ */

/* ---------- API + auth config ----------
   The front-end never holds a database key. All reads/writes go through the
   managed Functions API, and admin writes are protected by the Static Web
   Apps `admin` role (see staticwebapp.config.json). */
const API = "/api";

/* ---------- admin session token (password gate) ----------
   The console password is exchanged for a short-lived signed token by
   POST /api/login; we keep it in sessionStorage and attach it as a
   Bearer header on every request (harmless for the public endpoints,
   required for the authenticated operations folded onto /api/pages and
   /api/reports — see the API note in store/cloud functions). */
const TOKEN_KEY = "konform-docs-token";
// localStorage (not sessionStorage) so the sign-in survives closing the tab/browser.
function getToken() { try { return localStorage.getItem(TOKEN_KEY) || ""; } catch (e) { return ""; } }
function setToken(t) { try { if (t) localStorage.setItem(TOKEN_KEY, t); else localStorage.removeItem(TOKEN_KEY); } catch (e) {} }
function authHeaders() { const t = getToken(); return t ? { Authorization: "Bearer " + t } : {}; }

async function apiGet(path) {
  const r = await fetch(API + path, { headers: { Accept: "application/json", ...authHeaders() } });
  if (!r.ok) throw new Error("GET " + path + " → " + r.status);
  return r.json();
}
async function apiSend(method, path, body) {
  const opts = { method, headers: { ...authHeaders() } };
  if (body !== undefined) { opts.headers["Content-Type"] = "application/json"; opts.body = JSON.stringify(body); }
  const r = await fetch(API + path, opts);
  if (!r.ok) {
    let msg = method + " " + path + " → " + r.status;
    try { const e = await r.json(); if (e && e.error) msg = e.error; } catch (e2) {}
    throw new Error(msg);
  }
  try { return await r.json(); } catch (e) { return {}; }
}

/* Exchange the console password for a signed token (kept for this tab's session). */
async function adminLogin(password) {
  try {
    const r = await fetch(API + "/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ password }) });
    if (!r.ok) { let e; try { e = await r.json(); } catch (e2) {} return { ok: false, error: (e && e.error) || "Sign-in failed." }; }
    const d = await r.json();
    setToken(d.token);
    return { ok: true };
  } catch (e) { return { ok: false, error: "Couldn't reach the server." }; }
}
function adminLogout() { setToken(""); }
/* Confirm a stored token is still accepted by the API (clears it if not). */
async function adminPing() {
  if (!getToken()) return false;
  try {
    const r = await fetch(API + "/pages?ping=1", { headers: { Accept: "application/json", ...authHeaders() } });
    if (r.ok) return true;
    if (r.status === 401) setToken("");
    return false;
  } catch (e) { return false; }
}

/* Cloud mode is on by default (we expect the API to be there). cloudInit()
   flips it off if the very first request fails, so a backend-less static
   preview still works against localStorage. */
window.CLOUD = true;

const STORE_KEY = "konform-docs-userdata-v1";
const HIDDEN_SLUG = "__meta_hidden";   // reserved row that stores the hidden-id list
function clone(x) { return JSON.parse(JSON.stringify(x)); }

/* ---------- localStorage layer ---------- */
function loadUserData() {
  try {
    const raw = localStorage.getItem(STORE_KEY);
    if (!raw) return { pages: {}, deleted: [], removed: [], groupIcons: {}, groups: [], settings: {}, reports: [], pageOrder: [] };
    const d = JSON.parse(raw);
    return { pages: d.pages || {}, deleted: d.deleted || [], removed: d.removed || [], groupIcons: d.groupIcons || {}, groups: d.groups || [], settings: d.settings || {}, reports: d.reports || [], pageOrder: d.pageOrder || [] };
  } catch (e) { return { pages: {}, deleted: [], removed: [], groupIcons: {}, groups: [], settings: {}, reports: [], pageOrder: [] }; }
}
function saveUserData(data) {
  try { localStorage.setItem(STORE_KEY, JSON.stringify(data)); return true; }
  catch (e) { return false; }
}

/* ---------- cloud cache (populated by cloudInit) ---------- */
window.__cloud = { pages: {}, deleted: [], removed: [], groupIcons: {}, groups: [], settings: {}, pageOrder: [], loaded: false };

/* The single source rebuildLive reads from. */
function effectiveData() {
  return window.CLOUD ? window.__cloud : loadUserData();
}

/* ---------- revision history ----------
   Every save snapshots the *previous* content as a numbered revision and
   bumps the page's version (1.0 → 1.1 → 1.2 …). A page that has never been
   edited is version 1.0; its first edit files 1.0 into history and becomes 1.1.
   History is stored on the page itself (capped) so it travels with the page
   in both local and cloud mode. */
const REV_FIELDS = ["title", "lede", "group", "eyebrow", "meta", "blocks", "tags", "date", "cover", "iconKey", "draft", "parent"];
const REV_CAP = 3;

function revSnapshot(p) {
  const s = {};
  REV_FIELDS.forEach((k) => { if (p[k] !== undefined) s[k] = clone(p[k]); });
  return s;
}
function sameRevContent(a, b) {
  return JSON.stringify(revSnapshot(a)) === JSON.stringify(revSnapshot(b));
}
function nextVersion(v) {
  const m = String(v || "1.0").match(/^(\d+)\.(\d+)$/);
  if (!m) return "1.1";
  return m[1] + "." + (parseInt(m[2], 10) + 1);
}
/* The page as it stands BEFORE this save: a stored override if one exists,
   otherwise the built-in base template (so the first edit of a built-in files
   the original as v1.0). */
function priorStored(id) {
  const d = effectiveData();
  if (d.pages && d.pages[id]) return d.pages[id];
  let base = null;
  (window.BASE_NAV || []).forEach((g) => (g.items || []).filter(Boolean).forEach((p) => { if (p.id === id) base = p; }));
  (window.BASE_DEVLOG || []).forEach((p) => { if (p.id === id) base = p; });
  return base;
}
/* Attach version + revisions to a page about to be written. */
function applyRevision(page) {
  const prior = priorStored(page.id);
  if (prior && !sameRevContent(prior, page)) {
    const history = prior.revisions ? clone(prior.revisions) : [];
    history.push({ version: prior.version || "1.0", savedAt: prior.updatedAt || null, snapshot: revSnapshot(prior) });
    page.revisions = history.slice(-REV_CAP);
    page.version = nextVersion(prior.version || "1.0");
  } else if (prior) {
    page.revisions = prior.revisions ? clone(prior.revisions) : [];
    page.version = prior.version || "1.0";
  } else {
    page.revisions = Array.isArray(page.revisions) ? page.revisions : [];
    page.version = page.version || "1.0";
  }
  page.updatedAt = new Date().toISOString();
  return page;
}

/* ---------- unified write API (async) ---------- */
async function savePage(page, opts) {
  page = clone(page);
  if (!(opts && opts.noRevision)) applyRevision(page);
  if (window.CLOUD) {
    try {
      await apiSend("POST", "/pages", { slug: page.id, title: page.title, content: JSON.stringify(page) });
    } catch (e) { console.error(e); window.__lastError = (e && e.message) || String(e); return false; }
    window.__cloud.pages[page.id] = page;
  } else {
    const data = loadUserData();
    data.pages[page.id] = page;
    data.deleted = (data.deleted || []).filter((id) => id !== page.id);
    if (!saveUserData(data)) return false;
  }
  rebuildLive();
  window.dispatchEvent(new CustomEvent("konform-docs-updated"));
  return page;
}

/* Reset a built-in (drop override) OR delete a custom page. */
async function removePage(id) {
  if (window.CLOUD) {
    try { await apiSend("DELETE", "/pages?slug=" + encodeURIComponent(id)); }
    catch (e) { console.error(e); return false; }
    delete window.__cloud.pages[id];
  } else {
    const data = loadUserData();
    delete data.pages[id];
    data.deleted = (data.deleted || []).filter((d) => d !== id);
    saveUserData(data);
  }
  rebuildLive();
  window.dispatchEvent(new CustomEvent("konform-docs-updated"));
  return true;
}

async function restorePage(id) {
  if (window.CLOUD) return removePage(id);
  const data = loadUserData();
  data.deleted = (data.deleted || []).filter((d) => d !== id);
  delete data.pages[id];
  saveUserData(data);
  rebuildLive();
  window.dispatchEvent(new CustomEvent("konform-docs-updated"));
  return true;
}

/* ---------- cloud image upload ---------- */
async function uploadImageRemote(blob, ext) {
  if (!window.CLOUD) throw new Error("not in cloud mode");
  const r = await fetch(API + "/pages?upload=" + encodeURIComponent(ext || "png"), {
    method: "POST",
    headers: { "Content-Type": blob.type || "application/octet-stream", ...authHeaders() },
    body: blob,
  });
  if (!r.ok) {
    let msg = "upload failed (" + r.status + ")";
    try { const e = await r.json(); if (e && e.error) msg = e.error; } catch (e2) {}
    throw new Error(msg);
  }
  const data = await r.json();
  return data.url;
}

/* ---------- cloud bootstrap ---------- */
async function cloudInit() {
  if (!window.CLOUD) return;
  try {
    // Retry the first call — Azure Functions can cold-start for several
    // seconds, and we don't want a slow first response to wrongly drop a
    // deployed site into offline (localStorage) mode.
    let data, lastErr = null;
    for (let attempt = 0; attempt < 4; attempt++) {
      try { data = await apiGet("/pages"); lastErr = null; break; }
      catch (e) { lastErr = e; await new Promise((r) => setTimeout(r, 1500 * (attempt + 1))); }
    }
    if (lastErr) throw lastErr;
    const pages = {};
    let deleted = [], removed = [], groupIcons = {}, groups = [], settings = {}, pageOrder = [];
    (data || []).forEach((r) => {
      if (r.slug === HIDDEN_SLUG) {
        try { const meta = JSON.parse(r.content); deleted = meta.hidden || []; removed = meta.removed || []; groupIcons = meta.groupIcons || {}; groups = meta.groups || []; settings = meta.settings || {}; pageOrder = meta.pageOrder || []; } catch (e) {}
        return;   // never treat the meta row as a page
      }
      try { const p = JSON.parse(r.content); p.id = r.slug; pages[p.id] = p; } catch (e) {}
    });
    window.__cloud = { pages, deleted, removed, groupIcons, groups, settings, pageOrder, loaded: true };
    rebuildLive();
    window.dispatchEvent(new CustomEvent("konform-docs-updated"));
  } catch (e) {
    // No backend reachable → fall back to localStorage so the site still works.
    console.warn("API unreachable — falling back to offline (localStorage) mode.", e);
    window.CLOUD = false;
    rebuildLive();
    window.dispatchEvent(new CustomEvent("konform-docs-updated"));
  }
}

/* ---------- show / hide an entry on the live site ----------
   Works for built-in templates AND custom pages. Hiding never deletes
   content — it just adds the id to the persisted hidden-id set. */
function isHidden(id) {
  const d = (window.CLOUD ? window.__cloud : loadUserData()).deleted || [];
  return d.indexOf(id) !== -1;
}

async function setPageHidden(id, hidden) {
  if (window.CLOUD) {
    const set = new Set(window.__cloud.deleted || []);
    if (hidden) set.add(id); else set.delete(id);
    window.__cloud.deleted = Array.from(set);
    if (!(await writeCloudMeta())) return false;
  } else {
    const data = loadUserData();
    const set = new Set(data.deleted || []);
    if (hidden) set.add(id); else set.delete(id);
    data.deleted = Array.from(set);
    if (!saveUserData(data)) return false;
  }
  rebuildLive();
  window.dispatchEvent(new CustomEvent("konform-docs-updated"));
  return true;
}

/* Persist the site-meta row (hidden + removed + group-icon lists) in cloud mode. */
async function writeCloudMeta() {
  try {
    await apiSend("POST", "/pages", {
      slug: HIDDEN_SLUG,
      title: "(site meta)",
      content: JSON.stringify({ hidden: window.__cloud.deleted || [], removed: window.__cloud.removed || [], groupIcons: window.__cloud.groupIcons || {}, groups: window.__cloud.groups || [], settings: window.__cloud.settings || {}, pageOrder: window.__cloud.pageOrder || [] }),
    });
  } catch (e) { console.error(e); window.__lastError = (e && e.message) || String(e); return false; }
  return true;
}

/* Permanently remove a built-in template from BOTH the site and the console,
   or bring it back. (Custom pages use removePage instead.) Content is never
   touched — restoreBuiltins() returns every default exactly as it was. */
async function setBuiltinRemoved(id, removed) {
  if (window.CLOUD) {
    const set = new Set(window.__cloud.removed || []);
    if (removed) set.add(id); else set.delete(id);
    window.__cloud.removed = Array.from(set);
    if (!(await writeCloudMeta())) return false;
  } else {
    const data = loadUserData();
    const set = new Set(data.removed || []);
    if (removed) set.add(id); else set.delete(id);
    data.removed = Array.from(set);
    if (!saveUserData(data)) return false;
  }
  rebuildLive();
  window.dispatchEvent(new CustomEvent("konform-docs-updated"));
  return true;
}

async function restoreBuiltins() {
  if (window.CLOUD) {
    window.__cloud.removed = [];
    if (!(await writeCloudMeta())) return false;
  } else {
    const data = loadUserData();
    data.removed = [];
    if (!saveUserData(data)) return false;
  }
  rebuildLive();
  window.dispatchEvent(new CustomEvent("konform-docs-updated"));
  return true;
}

function removedIds() {
  return (window.CLOUD ? window.__cloud : loadUserData()).removed || [];
}

/* ---------- group icon overrides ---------- */
function getGroupIcons() {
  return (window.CLOUD ? window.__cloud : loadUserData()).groupIcons || {};
}

async function setGroupIcon(group, iconKey) {
  if (window.CLOUD) {
    const prevIcons = window.__cloud.groupIcons || {};
    const gi = { ...prevIcons };
    if (iconKey) gi[group] = iconKey; else delete gi[group];
    window.__cloud.groupIcons = gi;
    if (!(await writeCloudMeta())) { window.__cloud.groupIcons = prevIcons; return false; }
  } else {
    const data = loadUserData();
    const gi = { ...(data.groupIcons || {}) };
    if (iconKey) gi[group] = iconKey; else delete gi[group];
    data.groupIcons = gi;
    if (!saveUserData(data)) return false;
  }
  rebuildLive();
  window.dispatchEvent(new CustomEvent("konform-docs-updated"));
  return true;
}

/* ---------- group registry (create / delete sidebar sections) ---------- */
async function createGroup(name, iconKey) {
  name = String(name || "").trim();
  if (!name) return false;
  const isBuiltin = (window.BASE_NAV || []).some((g) => g.group === name);
  if (window.CLOUD) {
    const prevGroups = window.__cloud.groups || [];
    const prevIcons = window.__cloud.groupIcons || {};
    if (!isBuiltin && prevGroups.indexOf(name) === -1) window.__cloud.groups = [...prevGroups, name];
    if (iconKey) window.__cloud.groupIcons = { ...prevIcons, [name]: iconKey };
    // Roll back the in-memory change if the save didn't persist.
    if (!(await writeCloudMeta())) { window.__cloud.groups = prevGroups; window.__cloud.groupIcons = prevIcons; return false; }
  } else {
    const data = loadUserData();
    const list = data.groups || [];
    if (!isBuiltin && list.indexOf(name) === -1) data.groups = [...list, name];
    if (iconKey) { const gi = { ...(data.groupIcons || {}) }; gi[name] = iconKey; data.groupIcons = gi; }
    if (!saveUserData(data)) return false;
  }
  rebuildLive();
  window.dispatchEvent(new CustomEvent("konform-docs-updated"));
  return true;
}

/* Delete a user-created group. Refused for built-in groups or any group that still has pages. */
async function deleteGroup(name) {
  if ((window.BASE_NAV || []).some((g) => g.group === name)) return false;
  const d = effectiveData();
  const used = Object.values(d.pages || {}).some((p) => p.kind !== "devlog" && p.group === name);
  if (used) return false;
  if (window.CLOUD) {
    const prevGroups = window.__cloud.groups || [];
    const prevIcons = window.__cloud.groupIcons || {};
    window.__cloud.groups = prevGroups.filter((g) => g !== name);
    const gi = { ...prevIcons }; delete gi[name]; window.__cloud.groupIcons = gi;
    if (!(await writeCloudMeta())) { window.__cloud.groups = prevGroups; window.__cloud.groupIcons = prevIcons; return false; }
  } else {
    const data = loadUserData();
    data.groups = (data.groups || []).filter((g) => g !== name);
    const gi = { ...(data.groupIcons || {}) }; delete gi[name]; data.groupIcons = gi;
    if (!saveUserData(data)) return false;
  }
  rebuildLive();
  window.dispatchEvent(new CustomEvent("konform-docs-updated"));
  return true;
}

/* Push everything currently in localStorage up to the cloud (one-time migration). */
async function importLocalToCloud() {
  if (!window.CLOUD) return { ok: false, count: 0 };
  const local = loadUserData();
  const entries = Object.values(local.pages || {});
  let count = 0;
  for (const p of entries) {
    const ok = await savePage(p);
    if (ok) count++;
  }
  return { ok: true, count };
}

/* ---------- live merge ---------- */
function rebuildLive() {
  const data = effectiveData();
  const baseNav = window.BASE_NAV || [];
  const deleted = new Set(data.deleted || []);
  const removed = new Set(data.removed || []);
  const gIcons = data.groupIcons || {};
  const groupOrder = data.groups || [];
  const pageOrder = data.pageOrder || [];
  const gIdx = (g) => { const i = groupOrder.indexOf(g); return i === -1 ? 1e6 : i; };
  const pIdx = (id) => { const i = pageOrder.indexOf(id); return i === -1 ? 1e6 : i; };

  /* ---- FULL docs nav (includes hidden — the console needs these; removed are suppressed) ----
     Pages come from the store; any built-in templates (window.BASE_NAV, usually empty now)
     are merged in as fallbacks. Stored pages win. Order follows meta groups + pageOrder. */
  const pageMap = {};
  baseNav.forEach((g) => (g.items || []).filter(Boolean).forEach((p) => {
    if (!removed.has(p.id)) pageMap[p.id] = { ...p, group: p.group || g.group };
  }));
  Object.values(data.pages).forEach((p) => {
    if (p.kind === "devlog" || removed.has(p.id)) return;
    pageMap[p.id] = p;
  });

  const groupsByName = {};
  Object.values(pageMap).forEach((p) => { const g = p.group || "Custom"; (groupsByName[g] = groupsByName[g] || []).push(clone(p)); });
  const baseIconFor = (g) => { const b = baseNav.find((x) => x.group === g); return b && b.icon; };
  const nav = Object.keys(groupsByName)
    .sort((a, b) => gIdx(a) - gIdx(b) || a.localeCompare(b))
    .map((g) => ({
      group: g,
      icon: gIcons[g] || baseIconFor(g) || "layers",
      items: groupsByName[g].sort((a, b) => pIdx(a.id) - pIdx(b.id) || String(a.title).localeCompare(String(b.title))),
    }));

  const fullNav = nav.filter((g) => g.items.length > 0);
  const icon = { ...window.BASE_PAGE_ICON };
  const fullPageById = {};
  fullNav.forEach((g) => g.items.forEach((p) => {
    p.group = g.group;
    fullPageById[p.id] = p;
    if (p.iconKey) icon[p.id] = p.iconKey;
    if (!icon[p.id]) icon[p.id] = "doc";
  }));

  /* ---- FULL devlog (includes hidden) ---- */
  const dmap = {};
  (window.BASE_DEVLOG || []).forEach((p) => { if (!removed.has(p.id)) dmap[p.id] = clone(p); });
  Object.values(data.pages).forEach((p) => { if (p.kind === "devlog" && !removed.has(p.id)) dmap[p.id] = clone(p); });
  const fullDevlog = Object.values(dmap).sort((a, b) =>
    String(b.date || "").localeCompare(String(a.date || "")) || String(b.id).localeCompare(String(a.id)));
  const fullDevlogById = {};
  fullDevlog.forEach((p) => { fullDevlogById[p.id] = p; });

  /* ---- SITE-facing lists (hidden removed; drafts optionally hidden) ---- */
  const showDrafts = !(data.settings && data.settings.showDrafts === false);
  const visible = (p) => !deleted.has(p.id) && (showDrafts || !p.draft);
  const liveNav = fullNav
    .map((g) => ({ group: g.group, icon: g.icon, items: g.items.filter(visible) }))
    .filter((g) => g.items.length > 0);
  const pages = [], byId = {};
  liveNav.forEach((g) => g.items.forEach((p) => { pages.push(p); byId[p.id] = p; }));

  const devlog = fullDevlog.filter(visible);
  const devlogById = {};
  devlog.forEach((p) => { devlogById[p.id] = p; });

  /* ---- group registry: built-ins + user-created (for the console dropdowns) ---- */
  const allGroups = [];
  const pushG = (g) => { if (g && allGroups.indexOf(g) === -1) allGroups.push(g); };
  baseNav.forEach((g) => pushG(g.group));
  (data.groups || []).forEach(pushG);
  Object.values(data.pages).forEach((p) => { if (p.kind !== "devlog") pushG(p.group); });
  Object.keys(gIcons).forEach(pushG);
  const groupMeta = {};
  allGroups.forEach((g) => { const b = baseNav.find((x) => x.group === g); groupMeta[g] = gIcons[g] || (b && b.icon) || "layers"; });

  Object.assign(window, {
    NAV: liveNav, PAGES: pages, PAGE_BY_ID: byId, PAGE_ICON: icon,
    DEVLOG: devlog, DEVLOG_BY_ID: devlogById,
    ADMIN_NAV: fullNav, ADMIN_DEVLOG: fullDevlog,
    ADMIN_PAGE_BY_ID: { ...fullPageById, ...fullDevlogById },
    ALL_GROUPS: allGroups, GROUP_META: groupMeta,
  });
}

/* built-in id set (docs + seed devlog entries) */
window.BASE_IDS = new Set();
(window.BASE_NAV || []).forEach((g) => g.items.filter(Boolean).forEach((p) => window.BASE_IDS.add(p.id)));
(window.BASE_DEVLOG || []).forEach((p) => window.BASE_IDS.add(p.id));

/* ---------- site settings (stored in the meta row) ---------- */
const SETTING_DEFAULTS = {
  // General / branding
  siteTitle: "Konform",
  siteTitleAccent: "Docs",
  siteEyebrow: "Machinery Compliance Platform",
  siteTagline: "Guides, references and workflows for Konform — create Declarations of Conformity, run risk assessments, and keep the technical file export-ready for the EU Machinery Directive 2006/42/EC.",
  githubUrl: "",
  footerNote: "The official documentation for Konform — the machinery compliance platform for Declarations of Conformity, risk registers and audit-ready technical files.",
  defaultTheme: "light",
  // Header banner (home hero) — bannerImage is the light-theme picture; bannerImageDark is shown in dark theme (falls back to bannerImage)
  bannerImage: "",
  bannerImageDark: "",
  bannerPosY: 50,
  bannerHeight: 320,
  bannerOverlay: 38,
  bannerVignette: 0,
  bannerVignetteColor: "black",
  // Editor
  defaultGroup: "",
  autosave: false,
  showDrafts: true,
  // Reports
  reportsEnabled: true,
  reportCooldownSec: 20,
  reportArchiveDays: 0,
  // Access
  autoLogoutMin: 0,
};
function getSettings() {
  return (window.CLOUD ? window.__cloud : loadUserData()).settings || {};
}
function getSetting(key, fallback) {
  const v = getSettings()[key];
  return v === undefined ? fallback : v;
}
/* Setting value with the built-in default applied. */
function cfg(key) {
  const v = getSettings()[key];
  return v === undefined ? SETTING_DEFAULTS[key] : v;
}
async function setSetting(key, value) {
  if (window.CLOUD) {
    window.__cloud.settings = { ...(window.__cloud.settings || {}), [key]: value };
    if (!(await writeCloudMeta())) return false;
  } else {
    const data = loadUserData();
    data.settings = { ...(data.settings || {}), [key]: value };
    if (!saveUserData(data)) return false;
  }
  window.dispatchEvent(new CustomEvent("konform-docs-updated"));
  return true;
}

/* ---------- issue reports (Cosmos `reports` container; localStorage fallback) ---------- */
const REPORT_TYPES = ["Incorrect info", "Outdated", "Typo", "Broken link", "Other"];

async function submitReport(r) {
  // Honeypot: a bot that filled the hidden field gets a silent "success".
  if (r && r.hp) return { ok: true };
  const type = String((r && r.type) || "");
  const message = String((r && r.message) || "").trim();
  if (REPORT_TYPES.indexOf(type) === -1) return { ok: false, error: "Please choose an issue type." };
  if (message.length < 5) return { ok: false, error: "Please add a little more detail." };
  if (message.length > 1500) return { ok: false, error: "That's a bit long — keep it under 1500 characters." };
  try {
    const cooldownMs = (parseInt(cfg("reportCooldownSec"), 10) || 0) * 1000;
    const last = parseInt(localStorage.getItem("konform-report-last") || "0", 10);
    if (cooldownMs > 0 && Date.now() - last < cooldownMs) return { ok: false, error: "You just sent a report — give it a moment." };
  } catch (e) {}
  const rec = {
    page_id: String((r && r.pageId) || ""),
    page_title: String((r && r.pageTitle) || "").slice(0, 200),
    type, message: message.slice(0, 1500),
  };
  if (window.CLOUD) {
    try { await apiSend("POST", "/reports", rec); }
    catch (e) { console.error(e); return { ok: false, error: "Couldn't send right now — please try again later." }; }
  } else {
    const data = loadUserData();
    const local = { ...rec, id: "r-" + Date.now() + "-" + Math.random().toString(36).slice(2, 7), status: "new", created_at: new Date().toISOString() };
    data.reports = [local, ...(data.reports || [])];
    if (!saveUserData(data)) return { ok: false, error: "Couldn't save." };
  }
  try { localStorage.setItem("konform-report-last", String(Date.now())); } catch (e) {}
  window.dispatchEvent(new CustomEvent("konform-reports-updated"));
  return { ok: true };
}

/* Returns an array, or null if the reports endpoint is missing / errored. */
async function loadReports() {
  if (window.CLOUD) {
    try { return await apiGet("/reports"); }
    catch (e) { console.error("loadReports:", e.message || e); return null; }
  }
  return (loadUserData().reports || []);
}

async function setReportStatus(id, status) {
  if (window.CLOUD) {
    try { await apiSend("PATCH", "/reports?id=" + encodeURIComponent(id), { status }); }
    catch (e) { console.error(e); return false; }
  } else {
    const data = loadUserData();
    data.reports = (data.reports || []).map((x) => (x.id === id ? { ...x, status } : x));
    saveUserData(data);
  }
  window.dispatchEvent(new CustomEvent("konform-reports-updated"));
  return true;
}

async function deleteReport(id) {
  if (window.CLOUD) {
    try { await apiSend("DELETE", "/reports?id=" + encodeURIComponent(id)); }
    catch (e) { console.error(e); return false; }
  } else {
    const data = loadUserData();
    data.reports = (data.reports || []).filter((x) => x.id !== id);
    saveUserData(data);
  }
  window.dispatchEvent(new CustomEvent("konform-reports-updated"));
  return true;
}

/* ---------- data: export everything / wipe all content ---------- */
async function exportAllData() {
  const d = effectiveData();
  const pages = Object.values(d.pages || {}).filter((p) => p.kind !== "devlog");
  const devlogs = Object.values(d.pages || {}).filter((p) => p.kind === "devlog");
  let reports = [];
  try { const r = await loadReports(); if (Array.isArray(r)) reports = r; } catch (e) {}
  return {
    exportedAt: new Date().toISOString(),
    siteVersion: window.SITE_VERSION || null,
    settings: d.settings || {},
    groups: d.groups || [],
    groupIcons: d.groupIcons || {},
    pageOrder: d.pageOrder || [],
    hidden: d.deleted || [],
    pages, devlogs, reports,
  };
}

/* Permanently delete every page + devlog. Settings, groups and icons are kept. */
async function wipeAllContent() {
  if (window.CLOUD) {
    const ids = Object.keys(window.__cloud.pages || {});
    for (const id of ids) {
      try { await apiSend("DELETE", "/pages?slug=" + encodeURIComponent(id)); }
      catch (e) { console.error(e); return false; }
    }
    window.__cloud.pages = {};
    window.__cloud.deleted = [];
    window.__cloud.pageOrder = [];
    if (!(await writeCloudMeta())) return false;
  } else {
    const data = loadUserData();
    data.pages = {}; data.deleted = []; data.pageOrder = [];
    if (!saveUserData(data)) return false;
  }
  rebuildLive();
  window.dispatchEvent(new CustomEvent("konform-docs-updated"));
  return true;
}

/* Console identity is managed by Azure Static Web Apps (the identity
   provider owns the password), so there is nothing to change here in cloud
   mode. The localStorage fallback keeps a simple local gate password. */
async function changeConsolePassword(newPassword) {
  if (window.CLOUD) return { ok: false, error: "The console password is the ADMIN_PASSWORD app setting on your Static Web App — change it in the Azure portal (Configuration → Application settings)." };
  newPassword = String(newPassword || "");
  if (newPassword.length < 6) return { ok: false, error: "Use at least 6 characters." };
  const ok = await setSetting("localPassword", newPassword);
  return ok ? { ok: true } : { ok: false, error: "Couldn't save." };
}

Object.assign(window, {
  STORE_KEY, loadUserData, saveUserData, savePage, removePage, restorePage,
  rebuildLive, cloudInit, importLocalToCloud, uploadImageRemote, cloneData: clone,
  isHidden, setPageHidden, setBuiltinRemoved, restoreBuiltins, removedIds,
  getGroupIcons, setGroupIcon, createGroup, deleteGroup,
  getSetting, setSetting, cfg, SETTING_DEFAULTS,
  REPORT_TYPES, submitReport, loadReports, setReportStatus, deleteReport,
  exportAllData, wipeAllContent, changeConsolePassword,
  adminLogin, adminLogout, adminPing,
});

rebuildLive();
cloudInit();
