// Store, formatting, and app-wide helpers

const LS_KEY = "mab_quotes_state_v1";
const IDB_NAME = "mab_quotes_db";
const IDB_VERSION = 3;
const IDB_STORE = "state";
const IDB_FILES_STORE = "pdfs";
const IDB_KEY = "main";

// IndexedDB helpers — survive iOS Safari's 7-day purge of localStorage
// SINGLE schema: state + pdfs in the same DB at version 2.
function openIDB() {
  return new Promise((resolve, reject) => {
    if (!window.indexedDB) return reject(new Error("IndexedDB no soportado"));
    const req = indexedDB.open(IDB_NAME, IDB_VERSION);
    req.onupgradeneeded = () => {
      const db = req.result;
      // Drop any legacy stores left over from older builds
      const existing = Array.from(db.objectStoreNames);
      for (const name of existing) {
        if (name !== IDB_STORE && name !== IDB_FILES_STORE) {
          try { db.deleteObjectStore(name); } catch (e) {}
        }
      }
      if (!db.objectStoreNames.contains(IDB_STORE)) db.createObjectStore(IDB_STORE);
      if (!db.objectStoreNames.contains(IDB_FILES_STORE)) db.createObjectStore(IDB_FILES_STORE);
    };
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
    req.onblocked = () => console.warn("[MAB] IDB upgrade bloqueado — cierra otras pestañas de la app");
  });
}
window.openIDB = openIDB;
async function idbGet() {
  try {
    const db = await openIDB();
    return await new Promise((res, rej) => {
      const tx = db.transaction(IDB_STORE, "readonly");
      const req = tx.objectStore(IDB_STORE).get(IDB_KEY);
      req.onsuccess = () => res(req.result || null);
      req.onerror = () => rej(req.error);
    });
  } catch (e) { return null; }
}
async function idbPut(value) {
  try {
    const db = await openIDB();
    await new Promise((res, rej) => {
      const tx = db.transaction(IDB_STORE, "readwrite");
      tx.objectStore(IDB_STORE).put(value, IDB_KEY);
      tx.oncomplete = () => res();
      tx.onerror = () => rej(tx.error);
    });
    return true;
  } catch (e) { return false; }
}

// File / PDF helpers — use the SAME database opened by openIDB()
async function fileSave(file) {
  const db = await openIDB();
  const id = "f-" + Date.now() + "-" + Math.random().toString(36).slice(2, 6);
  const rec = { id, name: file.name, type: file.type, size: file.size, blob: file, savedAt: new Date().toISOString() };
  await new Promise((res, rej) => {
    const tx = db.transaction(IDB_FILES_STORE, "readwrite");
    tx.objectStore(IDB_FILES_STORE).put(rec, id);
    tx.oncomplete = () => res();
    tx.onerror = () => rej(tx.error);
  });
  return { id, name: file.name, type: file.type, size: file.size };
}
async function fileGet(id) {
  const db = await openIDB();
  return new Promise((res, rej) => {
    const tx = db.transaction(IDB_FILES_STORE, "readonly");
    const req = tx.objectStore(IDB_FILES_STORE).get(id);
    req.onsuccess = () => res(req.result || null);
    req.onerror = () => rej(req.error);
  });
}
async function fileDelete(id) {
  const db = await openIDB();
  return new Promise((res, rej) => {
    const tx = db.transaction(IDB_FILES_STORE, "readwrite");
    tx.objectStore(IDB_FILES_STORE).delete(id);
    tx.oncomplete = () => res();
    tx.onerror = () => rej(tx.error);
  });
}
async function fileOpenInNewTab(id) {
  const rec = await fileGet(id);
  if (!rec || !rec.blob) { alert("Archivo no encontrado"); return; }
  const url = URL.createObjectURL(rec.blob);
  const w = window.open(url, "_blank");
  if (!w) {
    const a = document.createElement("a");
    a.href = url; a.target = "_blank"; a.rel = "noopener";
    a.download = rec.name || "archivo.pdf"; document.body.appendChild(a); a.click(); a.remove();
  }
  setTimeout(() => URL.revokeObjectURL(url), 60000);
}
window.fileSave = fileSave;
window.fileGet = fileGet;
window.fileDelete = fileDelete;
window.fileOpenInNewTab = fileOpenInNewTab;

// Snapshot cached in memory after async IDB hydrate
window.__MAB_HYDRATED = false;

function defaultState() {
  return {
    materials: window.SEED_MATERIALS,
    roles: window.SEED_HH_ROLES,
    clients: [],
    quote: window.SEED_QUOTE,
    quotes: window.SEED_QUOTE_LIST,
    accounting: { entries: [] },
    route: "dashboard",
    tweaksOpen: false,
  };
}

// Normalize a loaded state shape — fixes any legacy data that was saved
// with `accounting: []` (old bug that caused the Contabilidad view to white-screen).
function normalizeState(s) {
  if (!s || typeof s !== "object") return defaultState();
  // Accounting: must be an object with .entries array.
  if (!s.accounting || Array.isArray(s.accounting)) {
    const legacy = Array.isArray(s.accounting) ? s.accounting : [];
    s.accounting = { entries: legacy };
  }
  if (!Array.isArray(s.accounting.entries)) s.accounting.entries = [];
  // Other arrays just in case
  if (!Array.isArray(s.materials)) s.materials = window.SEED_MATERIALS || [];
  if (!Array.isArray(s.roles))     s.roles     = window.SEED_HH_ROLES || [];
  if (!Array.isArray(s.clients))   s.clients   = [];
  if (!Array.isArray(s.quotes))    s.quotes    = [];
  if (!s.quote || typeof s.quote !== "object") s.quote = window.SEED_QUOTE;
  if (!Array.isArray(s.quote.items))    s.quote.items    = [];
  if (!Array.isArray(s.quote.hh))       s.quote.hh       = [];
  if (!Array.isArray(s.quote.expenses)) s.quote.expenses = [];
  return s;
}
window.normalizeState = normalizeState;

function loadState() {
  // Synchronous initial: try localStorage first (fast), then seeds.
  // IndexedDB hydration happens just after mount via hydrateFromIDB.
  try {
    const raw = localStorage.getItem(LS_KEY);
    if (raw) return normalizeState(JSON.parse(raw));
  } catch (e) {}
  return defaultState();
}

// Async — call once on app start to upgrade any local data with IDB version
async function hydrateFromIDB(setState) {
  try {
    const idbData = await idbGet();
    const lsRaw = localStorage.getItem(LS_KEY);
    const lsData = lsRaw ? JSON.parse(lsRaw) : null;
    // Prefer the one with more content (more materials / quotes)
    const score = (s) => s ? ((s.materials?.length || 0) + (s.quotes?.length || 0) * 10) : -1;
    const winner = score(idbData) > score(lsData) ? idbData : lsData;
    if (winner && score(winner) > score(lsData)) {
      // IDB had richer data — restore it
      const norm = normalizeState({ ...winner });
      setState(s => ({ ...norm, route: s.route }));
      console.log("[MAB] Datos restaurados desde IndexedDB (localStorage estaba vacío o reducido)");
    } else {
      // Even if we kept current state, normalize it (fixes legacy accounting:[] in place)
      setState(s => normalizeState({ ...s }));
    }
    window.__MAB_HYDRATED = true;
  } catch (e) {
    console.warn("[MAB] hydrateFromIDB falló", e);
  }
  // Request persistent storage so the browser doesn't purge data
  if (navigator.storage && navigator.storage.persist) {
    try {
      const granted = await navigator.storage.persist();
      window.__MAB_PERSISTENT = granted;
      if (!granted) console.warn("[MAB] Almacenamiento NO persistente — el navegador puede purgar datos.");
    } catch (e) {}
  }
}

// Tracks last successful save / current error so the UI can show feedback
window.__SAVE_STATUS = { lastSaved: null, error: null };

function saveState(s) {
  let lsOk = false;
  try {
    const { tweaksOpen, ...persist } = s;
    const json = JSON.stringify(persist);
    localStorage.setItem(LS_KEY, json);
    if (localStorage.getItem(LS_KEY) !== json) {
      throw new Error("El navegador no persistió los datos. ¿Modo incógnito?");
    }
    lsOk = true;
    window.__SAVE_STATUS = { lastSaved: new Date(), error: null };
    // Mirror to IndexedDB (async, fire-and-forget; more durable on iOS)
    idbPut(persist).catch(() => {});
  } catch (e) {
    // localStorage failed — try IndexedDB-only as last resort
    const { tweaksOpen, ...persist } = s;
    idbPut(persist).then(ok => {
      if (ok) {
        window.__SAVE_STATUS = { lastSaved: new Date(), error: null };
      } else {
        window.__SAVE_STATUS = { lastSaved: null, error: e.message || String(e) };
        console.error("[MAB] Error guardando datos:", e);
      }
      window.dispatchEvent(new CustomEvent("mab-save"));
    });
    return;
  }
  window.dispatchEvent(new CustomEvent("mab-save"));
}

function useSaveStatus() {
  const [, force] = React.useReducer(x => x + 1, 0);
  React.useEffect(() => {
    const fn = () => force();
    window.addEventListener("mab-save", fn);
    const tick = setInterval(force, 30000); // refresh "hace X min"
    return () => { window.removeEventListener("mab-save", fn); clearInterval(tick); };
  }, []);
  return window.__SAVE_STATUS;
}

function timeAgo(d) {
  if (!d) return "—";
  const s = Math.round((Date.now() - new Date(d).getTime()) / 1000);
  if (s < 5) return "ahora";
  if (s < 60) return `hace ${s}s`;
  const m = Math.round(s / 60);
  if (m < 60) return `hace ${m} min`;
  const h = Math.round(m / 60);
  return `hace ${h}h`;
}

// Currency (CLP, no decimals)
function clp(n) {
  if (isNaN(n) || n == null) return "$0";
  return "$" + Math.round(n).toLocaleString("es-CL");
}
function num(n) {
  if (isNaN(n) || n == null) return "0";
  const r = Math.round(n * 100) / 100;
  return r.toLocaleString("es-CL", { maximumFractionDigits: 2 });
}

// Compute line totals for an item. price is NET (unit), margin %.
function itemNet(it)    { return (+it.qty || 0) * (+it.price || 0); }
function itemMargin(it) { return itemNet(it) * ((+it.margin || 0) / 100); }
function itemClient(it) { return itemNet(it) + itemMargin(it); }

function hhNet(h) {
  const base = (+h.qty || 0) * (+h.rate || 0);
  const soc = base * ((+h.social || 0) / 100);
  return base + soc;
}
function hhBase(h) { return (+h.qty || 0) * (+h.rate || 0); }
function hhSocial(h) { return hhBase(h) * ((+h.social || 0) / 100); }

function expenseNet(e) { return (+e.qty || 0) * (+e.price || 0); }

function quoteTotals(q, tweaks) {
  const matNet   = q.items.reduce((a, it) => a + itemNet(it), 0);
  const matMarg  = q.items.reduce((a, it) => a + itemMargin(it), 0);
  const matClient = matNet + matMarg;
  const hhBaseTot = (q.hh || []).reduce((a, h) => a + hhBase(h), 0);
  const hhSocTot  = (q.hh || []).reduce((a, h) => a + hhSocial(h), 0);
  const hhTotal   = hhBaseTot + hhSocTot;
  const expTotal  = (q.expenses || []).reduce((a, e) => a + expenseNet(e), 0);
  const profit = q.profit || { enabled: false, percent: 0 };
  const profitBase = matClient + hhTotal + expTotal;
  const profitAmt = profit.enabled ? profitBase * ((+profit.percent || 0) / 100) : 0;
  const subtotal = profitBase + profitAmt;
  const ivaRate  = tweaks?.ivaEnabled ? (tweaks.ivaRate || 19) : 0;
  const iva      = Math.round(subtotal * ivaRate / 100);
  const total    = subtotal + iva;
  return { matNet, matMarg, matClient, hhBaseTot, hhSocTot, hhTotal, expTotal, profitAmt, subtotal, iva, ivaRate, total };
}

function uid(prefix = "x") {
  return prefix + "-" + Math.random().toString(36).slice(2, 8);
}

function statusBadge(s) {
  const map = {
    "borrador":  { cls: "", label: "Borrador" },
    "enviada":   { cls: "deep", label: "Enviada" },
    "aprobada":  { cls: "ok", label: "Aprobada" },
    "rechazada": { cls: "", label: "Rechazada" },
  };
  const m = map[s] || { cls: "", label: s };
  return <span className={`badge dot ${m.cls}`}>{m.label}</span>;
}

// Apply tweaks to CSS variables
function applyTweaks(t) {
  const r = document.documentElement;
  if (t.brandColor) r.style.setProperty("--brand", t.brandColor);
  if (t.brandDeep)  r.style.setProperty("--brand-deep", t.brandDeep);
  document.body.setAttribute("data-density", t.density || "comfortable");
  document.body.setAttribute("data-variant", t.variant || "v1");
}

// Icon helper (inline SVGs, 18px, currentColor stroke)
const I = (path, vb = "0 0 24 24") => (props) => (
  <svg width={props.size || 18} height={props.size || 18} viewBox={vb}
       fill="none" stroke="currentColor" strokeWidth="1.7"
       strokeLinecap="round" strokeLinejoin="round" {...props}>
    {path}
  </svg>
);

const Icon = {
  dashboard: I(<>
    <rect x="3" y="3" width="7" height="9" rx="1.5"/>
    <rect x="14" y="3" width="7" height="5" rx="1.5"/>
    <rect x="14" y="12" width="7" height="9" rx="1.5"/>
    <rect x="3" y="16" width="7" height="5" rx="1.5"/>
  </>),
  quote: I(<>
    <path d="M6 3h9l4 4v14a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1Z"/>
    <path d="M14 3v5h5"/>
    <path d="M8 13h8M8 17h5"/>
  </>),
  box: I(<>
    <path d="M12 2 3 7v10l9 5 9-5V7l-9-5Z"/>
    <path d="M3 7l9 5 9-5"/>
    <path d="M12 22V12"/>
  </>),
  users: I(<>
    <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
    <circle cx="9" cy="7" r="4"/>
    <path d="M22 21v-2a4 4 0 0 0-3-3.87"/>
    <path d="M16 3.13a4 4 0 0 1 0 7.75"/>
  </>),
  print: I(<>
    <path d="M6 9V3h12v6"/>
    <rect x="3" y="9" width="18" height="8" rx="1.5"/>
    <path d="M6 14h12v7H6z"/>
  </>),
  plus: I(<><path d="M12 5v14M5 12h14"/></>),
  trash: I(<>
    <path d="M3 6h18"/>
    <path d="M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2"/>
    <path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>
  </>),
  edit: I(<>
    <path d="M12 20h9"/>
    <path d="M16.5 3.5a2.121 2.121 0 1 1 3 3L7 19l-4 1 1-4 12.5-12.5z"/>
  </>),
  download: I(<>
    <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
    <path d="M7 10l5 5 5-5"/>
    <path d="M12 15V3"/>
  </>),
  upload: I(<>
    <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
    <path d="M17 8l-5-5-5 5"/>
    <path d="M12 3v12"/>
  </>),
  search: I(<>
    <circle cx="11" cy="11" r="7"/>
    <path d="M21 21l-4.3-4.3"/>
  </>),
  settings: I(<>
    <circle cx="12" cy="12" r="3"/>
    <path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3h0a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8v0a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1Z"/>
  </>),
  check: I(<><path d="M20 6 9 17l-5-5"/></>),
  copy: I(<>
    <rect x="9" y="9" width="13" height="13" rx="2"/>
    <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
  </>),
  external: I(<>
    <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
    <path d="M15 3h6v6"/>
    <path d="M10 14 21 3"/>
  </>),
  arrow: I(<><path d="M5 12h14M13 5l7 7-7 7"/></>),
  bolt: I(<><path d="M13 2 3 14h8l-1 8 10-12h-8l1-8Z"/></>),
  wallet: I(<>
    <path d="M3 7v10a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2H5a2 2 0 0 1 0-4h11"/>
    <circle cx="17" cy="13" r="1.2" fill="currentColor"/>
  </>),
};

window.loadState = loadState;
window.saveState = saveState;
window.clp = clp;
window.num = num;
window.itemNet = itemNet;
window.itemMargin = itemMargin;
window.itemClient = itemClient;
window.hhNet = hhNet;
window.hhBase = hhBase;
window.hhSocial = hhSocial;
window.expenseNet = expenseNet;
window.quoteTotals = quoteTotals;
window.uid = uid;
window.statusBadge = statusBadge;
window.applyTweaks = applyTweaks;
window.Icon = Icon;
window.fileSave = fileSave;
window.fileGet = fileGet;
window.fileDelete = fileDelete;
window.fileOpenInNewTab = fileOpenInNewTab;
