// Novel store — single source of truth for arcs + chapters + characters + cards.
// Phase 2: backed by Supabase via /api/novel (was localStorage in phase 1).
//
// Public surface (preserved so admin/reader/gallery don't change):
//   window.NovelStore.useNovelStore() → [store, setStore]
//   window.NovelStore.load()          → snapshot of the cached store (sync)
//   window.NovelStore.save(store)     → optimistic write + PUT /api/novel
//   window.NovelStore.filterForReader(store) → arcs with at least one written chapter
//   window.NovelStore.newId()         → uuid v4 (matches DB column type)
//   window.NovelStore.pad2(n), preset(i), ARC_PRESETS  (used by admin to create arcs)
//   window.NovelStore.uploadImage(file) → POST /api/upload, returns the public URL

const NOVEL_CACHE_KEY = "trooper_novel_cache_v3";

const ARC_PRESETS = [
  { spine: { bg: "#8b1a2a", stripe: "#efe2c4", text: "#fff3d9" }, cover: { bg: "linear-gradient(180deg, #3a0d16 0%, #8b1a2a 100%)", accent: "#efe2c4", tag: "ACTION" } },
  { spine: { bg: "#0f2e4a", stripe: "#6fd8c3", text: "#cfe9e2" }, cover: { bg: "linear-gradient(160deg, #0a1a2a 0%, #0f2e4a 60%, #163f66 100%)", accent: "#6fd8c3", tag: "MYSTERY" } },
  { spine: { bg: "#1d1a24", stripe: "#ff5aa0", text: "#ffcfe0" }, cover: { bg: "linear-gradient(180deg, #0b0914 0%, #1d1a24 100%)", accent: "#ff5aa0", tag: "THRILLER" } },
  { spine: { bg: "#2a1a0a", stripe: "#e4b35a", text: "#f4d89a" }, cover: { bg: "linear-gradient(180deg, #120a03 0%, #2a1a0a 100%)", accent: "#e4b35a", tag: "DRAMA" } },
  { spine: { bg: "#123a2f", stripe: "#a8d08a", text: "#d4eac4" }, cover: { bg: "linear-gradient(160deg, #041a12 0%, #123a2f 100%)", accent: "#a8d08a", tag: "CYBER" } },
  { spine: { bg: "#f2ebdc", stripe: "#4a3820", text: "#1a1208" }, cover: { bg: "linear-gradient(180deg, #f2ebdc 0%, #dcd0b8 100%)", accent: "#a27a2a", tag: "CORE", dark: true } },
  { spine: { bg: "#3a1830", stripe: "#c96ad8", text: "#f0d8f4" }, cover: { bg: "linear-gradient(160deg, #1a0818 0%, #3a1830 100%)", accent: "#c96ad8", tag: "HORROR" } },
  { spine: { bg: "#0a0812", stripe: "#ff3a3a", text: "#ffcfcf" }, cover: { bg: "linear-gradient(180deg, #000 0%, #0a0812 60%, #2a0608 100%)", accent: "#ff3a3a", tag: "FINALE" } },
];

function preset(i) { return ARC_PRESETS[i % ARC_PRESETS.length]; }
function pad2(n) { return String(n).padStart(2, "0"); }

function newId() {
  if (window.crypto && window.crypto.randomUUID) return window.crypto.randomUUID();
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
    const r = Math.random() * 16 | 0;
    const v = c === "x" ? r : (r & 0x3) | 0x8;
    return v.toString(16);
  });
}

function emptyStore() { return { arcs: [], characters: [], cards: [] }; }

// Always returns an object with arcs/characters/cards as arrays, no matter
// what's in localStorage (handles legacy/corrupt cached values).
function normalize(maybe) {
  return {
    arcs: Array.isArray(maybe?.arcs) ? maybe.arcs : [],
    characters: [],
    cards: Array.isArray(maybe?.cards) ? maybe.cards : [],
  };
}

// Renombradas a *NovelCache para evitar colisión global con saveCache/loadCache
// de cardSystem.jsx — los <script type="text/babel"> comparten scope global, y
// cardSystem cargaba después, sobrescribiendo estas y corrompiendo el storage
// de user_cards con datos del novelStore (bug del 2026-04-26 noche).
function loadNovelCache() {
  try {
    const raw = localStorage.getItem(NOVEL_CACHE_KEY);
    if (!raw) return emptyStore();
    return normalize(JSON.parse(raw));
  } catch (e) { return emptyStore(); }
}

function saveNovelCache(store) {
  try { localStorage.setItem(NOVEL_CACHE_KEY, JSON.stringify(normalize(store))); } catch (e) {}
}

function loadStore() { return loadNovelCache(); }

async function fetchStore() {
  try {
    const r = await fetch("/api/novel", { credentials: "same-origin" });
    if (!r.ok) throw new Error(`HTTP ${r.status}`);
    const store = await r.json();
    const normalized = normalize(store);
    saveNovelCache(normalized);
    return normalized;
  } catch (e) {
    console.error("[novelStore] fetch failed", e);
    return loadNovelCache();
  }
}

async function saveStore(store) {
  const normalized = normalize(store);
  saveNovelCache(normalized);
  window.dispatchEvent(new CustomEvent("trooper:novel-updated"));
  try {
    const r = await fetch("/api/novel", {
      method: "PUT",
      headers: { "Content-Type": "application/json" },
      credentials: "same-origin",
      body: JSON.stringify(normalized),
    });
    if (!r.ok) {
      const txt = await r.text().catch(() => "");
      console.error("[novelStore] save failed", r.status, txt);
      window.dispatchEvent(new CustomEvent("trooper:novel-save-error", { detail: { status: r.status, body: txt } }));
    }
  } catch (e) {
    console.error("[novelStore] save error", e);
  }
}

function filterForReader(store) {
  const out = (store.arcs || [])
    .map(a => ({
      ...a,
      chapters: (a.chapters || []).filter(
        c => c.body && c.body.some(p => p && p.trim().length > 0)
      ),
    }))
    .filter(a => a.chapters.length > 0)
    .map((a, idx) => ({
      ...a,
      num: pad2(idx + 1),
      year: `ARCO · ${pad2(idx + 1)}`,
      chapters: a.chapters.map((c, j) => ({ ...c, num: pad2(j + 1) })),
    }));
  return out;
}

function useNovelStore() {
  const [store, setStore] = React.useState(() => loadNovelCache());

  React.useEffect(() => {
    let cancelled = false;
    fetchStore().then((s) => { if (!cancelled) setStore(normalize(s)); });

    const refresh = () => setStore(loadNovelCache());
    const refetch = () => fetchStore().then((s) => { if (!cancelled) setStore(normalize(s)); });
    window.addEventListener("trooper:novel-updated", refresh);
    window.addEventListener("trooper:user-changed", refetch);
    return () => {
      cancelled = true;
      window.removeEventListener("trooper:novel-updated", refresh);
      window.removeEventListener("trooper:user-changed", refetch);
    };
  }, []);

  // Wrap setter so any caller passing a partial/weird store still results
  // in a fully-shaped store (the admin reads .arcs and .cards directly).
  const save = (next) => {
    const safe = normalize(next);
    setStore(safe);
    saveStore(safe);
  };
  return [store, save];
}

async function uploadImage(file) {
  if (!file || !file.type || !file.type.startsWith("image/")) {
    throw new Error("Solo se permiten imágenes");
  }
  if (file.size > 8 * 1024 * 1024) {
    throw new Error("La imagen es muy grande (máximo 8MB)");
  }
  const fd = new FormData();
  fd.append("file", file);
  const r = await fetch("/api/upload", {
    method: "POST",
    credentials: "same-origin",
    body: fd,
  });
  if (!r.ok) {
    const data = await r.json().catch(() => ({}));
    throw new Error(data.error || `Error subiendo imagen (${r.status})`);
  }
  const { url } = await r.json();
  return url;
}

window.NovelStore = {
  load: loadStore,
  save: saveStore,
  saveStore,
  fetch: fetchStore,
  filterForReader,
  newId,
  pad2,
  preset,
  ARC_PRESETS,
  useNovelStore,
  uploadImage,
};
