// talon-store.jsx — admin overrides: merge base POIs with user edits/adds/deletes.
// Persistence is delegated to the SQLite layer (window.TalonDB). Pure helpers
// return NEW objects so React state updates cleanly.

// overrides are now read from SQLite at startup (see App). saveOverrides writes
// every mutation back through the DB layer.
function saveOverrides(o) {
  if (window.TalonDB) window.TalonDB.write(o);
}

const VALID_CATEGORIES = ["city", "ruin", "landmark", "character", "region"];

function regionOverride(o, regionId) {
  return o.regions[regionId] || { edits: {}, deletes: [], order: [] };
}

// effective POI list for a region (base ∖ deletes, then edits/adds applied)
function computePois(region, o) {
  const ro = regionOverride(o, region.id);
  const deletes = new Set(ro.deletes || []);
  const map = new Map();
  const order = [];
  region.pois.forEach((p) => { if (!deletes.has(p.id)) { map.set(p.id, p); order.push(p.id); } });
  const edits = ro.edits || {};
  // appended customs keep insertion order from ro.order (ids not in base)
  (ro.order || []).forEach((id) => { if (edits[id] && !map.has(id)) { map.set(id, edits[id]); order.push(id); } });
  Object.keys(edits).forEach((id) => {
    if (map.has(id)) map.set(id, edits[id]);          // override base
    else if (!order.includes(id)) { map.set(id, edits[id]); order.push(id); } // safety
  });
  return order.map((id) => map.get(id)).filter(Boolean);
}

function isBasePoi(region, poiId) {
  return region.pois.some((p) => p.id === poiId);
}
function isCustomPoi(region, o, poiId) {
  return !isBasePoi(region, poiId) && !!(regionOverride(o, region.id).edits || {})[poiId];
}
function isEdited(region, o, poiId) {
  const ro = regionOverride(o, region.id);
  if ((ro.deletes || []).includes(poiId)) return true;
  return !!(ro.edits || {})[poiId];
}

// upsert a full poi object (used for both base-edits and new customs)
function withUpsert(o, regionId, poi) {
  const next = { regions: { ...o.regions } };
  const ro = { ...regionOverride(o, regionId) };
  ro.edits = { ...(ro.edits || {}), [poi.id]: poi };
  ro.deletes = (ro.deletes || []).filter((id) => id !== poi.id);
  ro.order = (ro.order || []).includes(poi.id) ? ro.order : [...(ro.order || []), poi.id];
  next.regions[regionId] = ro;
  saveOverrides(next);
  return next;
}

function withDelete(o, regionId, poiId, basePoi) {
  const next = { regions: { ...o.regions } };
  const ro = { ...regionOverride(o, regionId) };
  const edits = { ...(ro.edits || {}) }; delete edits[poiId];
  ro.edits = edits;
  ro.order = (ro.order || []).filter((id) => id !== poiId);
  ro.deletes = basePoi ? Array.from(new Set([...(ro.deletes || []), poiId])) : (ro.deletes || []);
  next.regions[regionId] = ro;
  saveOverrides(next);
  return next;
}

// revert a single base POI back to its shipped definition
function withRevert(o, regionId, poiId) {
  const next = { regions: { ...o.regions } };
  const ro = { ...regionOverride(o, regionId) };
  const edits = { ...(ro.edits || {}) }; delete edits[poiId];
  ro.edits = edits;
  ro.deletes = (ro.deletes || []).filter((id) => id !== poiId);
  ro.order = (ro.order || []).filter((id) => id !== poiId);
  next.regions[regionId] = ro;
  saveOverrides(next);
  return next;
}

function clearRegion(o, regionId) {
  const next = { regions: { ...o.regions } };
  delete next.regions[regionId];
  saveOverrides(next);
  return next;
}

function makePoiId() { return "poi-" + Date.now().toString(36) + Math.floor(Math.random() * 1e3).toString(36); }

function blankPoi(coords) {
  return {
    id: makePoiId(),
    title: "New Point of Interest",
    category: "landmark",
    subtitle: null,
    coords: { x: +coords.x.toFixed(4), y: +coords.y.toFixed(4) },
    tags: [],
    body: [{ h: null, p: "" }],
    related: [],
    hero: null,
  };
}

// ── export ─────────────────────────────────────────────────────
function jsValue(v, indent) {
  if (v === null || v === undefined) return "null";
  if (typeof v === "number") return String(v);
  if (typeof v === "boolean") return String(v);
  if (typeof v === "string") return JSON.stringify(v);
  if (Array.isArray(v)) {
    if (v.length === 0) return "[]";
    const simple = v.every((x) => typeof x !== "object" || x === null);
    if (simple) return "[" + v.map((x) => jsValue(x)).join(", ") + "]";
    const pad = "  ".repeat(indent + 1);
    return "[\n" + v.map((x) => pad + jsValue(x, indent + 1)).join(",\n") + "\n" + "  ".repeat(indent) + "]";
  }
  // object
  const pad = "  ".repeat(indent + 1);
  const keys = Object.keys(v);
  return "{ " + keys.map((k) => `${k}: ${jsValue(v[k], indent + 1)}`).join(", ") + " }";
}

function exportRegionsText(regions, o) {
  const lines = [];
  lines.push("// Talon — exported POI data. Paste the `pois` arrays into talon-data.jsx.");
  regions.forEach((r) => {
    const pois = computePois(r, o);
    lines.push("");
    lines.push(`// ── ${r.name} (${r.id}) — ${pois.length} POIs ──`);
    lines.push(`pois: [`);
    pois.forEach((p) => { lines.push("  " + jsValue(p, 1) + ","); });
    lines.push(`],`);
  });
  return lines.join("\n");
}

// ── JSON snapshot export / import ──────────────────────────────
// A portable snapshot of the EFFECTIVE POIs per region (base + overrides merged).
function exportSnapshot(regions, o) {
  const out = { talonMap: 1, version: 1, exported: new Date().toISOString(), regions: {} };
  regions.forEach((r) => { out.regions[r.id] = { name: r.name, pois: computePois(r, o) }; });
  return out;
}
function exportSnapshotJSON(regions, o) {
  return JSON.stringify(exportSnapshot(regions, o), null, 2);
}

function normalizePoi(p, fallbackId) {
  const id = p.id || fallbackId || makePoiId();
  const cx = p.coords && typeof p.coords.x === "number" ? p.coords.x : 0.5;
  const cy = p.coords && typeof p.coords.y === "number" ? p.coords.y : 0.5;
  let body = Array.isArray(p.body) ? p.body.map((b) => ({ h: b && b.h != null ? String(b.h) : null, p: b && b.p != null ? String(b.p) : "" })) : [{ h: null, p: "" }];
  if (body.length === 0) body = [{ h: null, p: "" }];
  return {
    id,
    title: p.title != null ? String(p.title) : "Untitled",
    category: VALID_CATEGORIES.includes(p.category) ? p.category : "landmark",
    subtitle: p.subtitle ? String(p.subtitle) : null,
    coords: { x: +Math.min(1, Math.max(0, cx)).toFixed(4), y: +Math.min(1, Math.max(0, cy)).toFixed(4) },
    tags: Array.isArray(p.tags) ? p.tags.map(String) : [],
    body,
    related: Array.isArray(p.related) ? p.related.map(String) : [],
    hero: p.hero ? String(p.hero) : null,
  };
}

// Apply imported POIs to a region so the EFFECTIVE list equals the import.
function applyImportToRegion(o, region, importedPois) {
  const next = { regions: { ...o.regions } };
  const importedIds = new Set();
  const edits = {};
  const order = [];
  (importedPois || []).forEach((raw, i) => {
    const poi = normalizePoi(raw, region.id + "-import-" + i);
    edits[poi.id] = poi;
    importedIds.add(poi.id);
    if (!region.pois.some((b) => b.id === poi.id)) order.push(poi.id);
  });
  const deletes = region.pois.filter((b) => !importedIds.has(b.id)).map((b) => b.id);
  next.regions[region.id] = { edits, deletes, order };
  saveOverrides(next);
  return next;
}

// Import a full snapshot (or a bare array → applied to activeRegion).
// Returns { overrides, applied:[regionIds], skipped:[ids], error }.
function importSnapshot(o, regions, parsed, activeRegion) {
  let next = o, applied = [], skipped = [];
  const byId = {}; regions.forEach((r) => (byId[r.id] = r));
  if (Array.isArray(parsed)) {
    if (!activeRegion) return { overrides: o, applied, skipped, error: "No active region for array import." };
    next = applyImportToRegion(next, activeRegion, parsed);
    applied.push(activeRegion.id);
    return { overrides: next, applied, skipped };
  }
  if (parsed && parsed.regions && typeof parsed.regions === "object") {
    Object.keys(parsed.regions).forEach((rid) => {
      const region = byId[rid];
      const pois = parsed.regions[rid] && parsed.regions[rid].pois;
      if (region && Array.isArray(pois)) { next = applyImportToRegion(next, region, pois); applied.push(rid); }
      else skipped.push(rid);
    });
    return { overrides: next, applied, skipped };
  }
  return { overrides: o, applied, skipped, error: "Unrecognized JSON shape. Expected { regions: { <id>: { pois:[] } } } or a POI array." };
}

Object.assign(window, {
  saveOverrides, computePois, isBasePoi, isCustomPoi, isEdited,
  withUpsert, withDelete, withRevert, clearRegion, blankPoi, makePoiId, exportRegionsText,
  exportSnapshot, exportSnapshotJSON, normalizePoi, applyImportToRegion, importSnapshot,
});
