/* SkyOps — Commercial model. Two sides of the same contract:
   · Provider (drone service company): quotes → contracts → jobs → invoices → credit notes (incl. SLA penalties)
   · Owner (asset owner): supplier contracts → purchase orders → supplier invoices (3-way match, SLA penalties) → budget vs actual
   Persisted in skyops_cm_v2. Dates are seeded relative to the app's "today". */

const CM_PB_DEFAULT = { perHa: 18, mob: 1200, refly: 650, rush: 20, ortho: 4, model3d: 6, warranty: 350, pilotDay: 420, droneHour: 38, computeHa: 1.1, reviewHour: 55 };
const CM_CLIENTS = { "pt-ev-03": "Iberdrola Renovables", "es-mu-02": "Iberdrola Renovables", "es-zr-07": "Forestalia", "es-ab-01": "Solaria Energía", "es-se-04": "Solaria Energía", "es-ca-05": "Endesa X", "pt-al-06": "EDP Renováveis" };
const cmClient = (s) => (s && (s.owner || CM_CLIENTS[s.id])) || "Iberdrola Renovables";
const cmEur = (n) => window.skyFmt.money(n);
const cmAdd = (d, n) => window.skyAdd(n, d);
const cmT = () => window.SKY_TODAY;
const cmWd = (a, b) => { let n = 0, t = new Date(a + "T00:00:00Z"); const e = new Date(b + "T00:00:00Z"); while (t < e) { t.setUTCDate(t.getUTCDate() + 1); const d = t.getUTCDay(); if (d && d !== 6) n++; } return n; };
const cmH = (s) => { let h = 7; for (const c of String(s)) h = (h * 31 + c.charCodeAt(0)) % 9973; return h; };
const cmSite = (id) => (window.ALL_SITES || window.SITES || []).find(s => s.id === id) || { id, name: id };
const CM_CATS = { inspection: "Inspection", cleaning: "Cleaning", vegetation: "Vegetation", repairs: "Repairs & spares" };

function cmSeed() {
  const T = cmT(), A = (n) => cmAdd(T, n);
  return {
    pb: CM_PB_DEFAULT,
    quotes: [
      { id: "Q-2026-031", client: "Endesa X", siteId: "es-ca-05", area: 250.9, cycles: 4, opts: { ortho: true }, rush: false, status: "sent", date: A(-9), valid: A(21) },
      { id: "Q-2026-029", client: "EDP Renováveis", siteId: "pt-al-06", area: 176.4, cycles: 2, opts: { ortho: true, warranty: true }, rush: false, status: "accepted", date: A(-26), valid: A(4), contractId: "CT-2026-006" },
      { id: "Q-2026-027", client: "Solaria Energía", siteId: "es-se-04", area: 114.5, cycles: 4, opts: {}, rush: true, status: "declined", date: A(-45), valid: A(-15) },
      { id: "Q-2026-033", client: "Solaria Energía", siteId: "es-ab-01", area: 322.0, cycles: 4, opts: {}, rush: false, status: "draft", date: A(-1), valid: A(29) },
    ],
    contracts: [
      { id: "CT-2025-011", client: "Iberdrola Renovables", siteId: "pt-ev-03", start: "2025-07-01", end: "2026-06-30", cycles: 4, price: 8950, opts: { ortho: true }, sla: { days: 5, coverage: 95, pct: 2, cap: 10 }, status: "active" },
      { id: "CT-2025-014", client: "Iberdrola Renovables", siteId: "es-mu-02", start: "2025-10-01", end: "2026-09-30", cycles: 4, price: 7400, opts: {}, sla: { days: 5, coverage: 95, pct: 2, cap: 10 }, status: "active" },
      { id: "CT-2026-002", client: "Forestalia", siteId: "es-zr-07", start: "2026-01-01", end: "2026-12-31", cycles: 2, price: 9800, opts: { model3d: true }, sla: { days: 5, coverage: 95, pct: 3, cap: 15 }, status: "active" },
      { id: "CT-2026-006", client: "EDP Renováveis", siteId: "pt-al-06", start: A(14), end: A(379), cycles: 2, price: 5100, opts: { ortho: true, warranty: true }, sla: { days: 5, coverage: 95, pct: 2, cap: 10 }, status: "signed", quoteId: "Q-2026-029" },
    ],
    inv: {}, cn: [],
    sContracts: [
      { id: "SC-2026-01", supplier: "AeroScan Ibérica", cat: "inspection", scope: "Drone thermography · overflow & ES sites", rate: "€4,200 / MIC + €18 / ha", start: "2026-01-01", end: "2026-12-31", cap: 160000, sla: { days: 5, pct: 2, cap: 10 } },
      { id: "SC-2026-02", supplier: "CleanTech Iberia", cat: "cleaning", scope: "Robotic & manual module cleaning", rate: "€1.30 / module", start: "2026-01-01", end: "2026-12-31", cap: 90000, sla: { days: 5, pct: 1, cap: 5 } },
      { id: "SC-2026-03", supplier: "Verde Sul", cat: "vegetation", scope: "Vegetation management · Alentejo", rate: "€310 / ha", start: "2026-02-01", end: "2027-01-31", cap: 40000, sla: { days: 10, pct: 1, cap: 5 } },
    ],
    pos: [
      { id: "PO-26-0141", supplier: "AeroScan Ibérica", contract: "SC-2026-01", siteId: "es-zr-07", cat: "inspection", desc: "MIC-2026-04-031 · Zaragoza Q2 thermography", amount: 11850, date: A(-20), status: "received", mic: "MIC-2026-04-031" },
      { id: "PO-26-0138", supplier: "CleanTech Iberia", contract: "SC-2026-02", siteId: "pt-ev-03", cat: "cleaning", desc: "WO-2026-0415 · robotic wash sector C · 1,840 modules", amount: 2392, date: A(-12), status: "approved", wo: "WO-2026-0415" },
      { id: "PO-26-0131", supplier: "Verde Sul", contract: "SC-2026-03", siteId: "pt-ev-03", cat: "vegetation", desc: "WO-2026-0398 · NW tree line clearance", amount: 1860, date: A(-48), status: "closed", wo: "WO-2026-0398" },
      { id: "PO-26-0127", supplier: "AeroScan Ibérica", contract: "SC-2026-01", siteId: "es-mu-02", cat: "inspection", desc: "Lorca re-fly sectors 3–4", amount: 6400, date: A(-62), status: "closed" },
      { id: "PO-26-0144", supplier: "CleanTech Iberia", contract: "SC-2026-02", siteId: "es-mu-02", cat: "cleaning", desc: "Lorca wash before summer peak · 9,200 modules", amount: 11960, date: A(-2), status: "pending" },
    ],
    sInv: [
      { id: "AS-2026-0412", supplier: "AeroScan Ibérica", po: "PO-26-0141", amount: 11850, date: A(-6), due: A(24), status: "pending", wd: 7 },
      { id: "AS-2026-0371", supplier: "AeroScan Ibérica", po: "PO-26-0127", amount: 6400, date: A(-50), due: A(-20), status: "paid", wd: 4, paidAt: A(-22) },
      { id: "VS-0088", supplier: "Verde Sul", po: "PO-26-0131", amount: 1980, date: A(-40), due: A(-10), status: "disputed", note: "Invoice exceeds PO by €120 · extra 0.4 ha not authorised" },
      { id: "CTI-26-1190", supplier: "CleanTech Iberia", po: "PO-26-0131x", amount: 3150, date: A(-70), due: A(-40), status: "paid", paidAt: A(-41), siteId: "es-mu-02", cat: "cleaning" },
    ],
    budget: { "pt-ev-03": { inspection: 36000, cleaning: 42000, vegetation: 9000, repairs: 60000 }, "es-mu-02": { inspection: 30000, cleaning: 38000, vegetation: 4000, repairs: 45000 }, "es-zr-07": { inspection: 24000, cleaning: 20000, vegetation: 6000, repairs: 30000 } },
  };
}
const cmStore = {
  v: (() => { try { const s = localStorage.getItem("skyops_cm_v2"); if (s) return JSON.parse(s); } catch {} return cmSeed(); })(),
  subs: new Set(),
  set(fn) { this.v = fn(this.v); try { localStorage.setItem("skyops_cm_v2", JSON.stringify(this.v)); } catch {} this.subs.forEach(s => s(this.v)); },
};
function useCM() { const [v, setV] = React.useState(cmStore.v); React.useEffect(() => { cmStore.subs.add(setV); return () => cmStore.subs.delete(setV); }, []); return v; }

/* Period filter */
const CM_PERIODS = [["month", "Month"], ["quarter", "Quarter"], ["year", "Year"], ["all", "All"]];
function cmRange(p) {
  const T = cmT(), [y, m] = T.split("-").map(Number);
  if (p === "month") return [`${y}-${String(m).padStart(2, "0")}-01`, T];
  if (p === "quarter") return [`${y}-${String(Math.floor((m - 1) / 3) * 3 + 1).padStart(2, "0")}-01`, T];
  if (p === "year") return [`${y}-01-01`, T];
  return ["0000", "9999"];
}
const cmIn = (d, r) => !!d && d >= r[0] && d <= r[1];
const cmPeriodLabel = (p) => { const r = cmRange(p); return p === "all" ? "all time" : `${window.skyFmt.date(r[0])} → ${window.skyFmt.date(r[1])}`; };

/* Provider: price for a quote / contract */
function cmQuoteTotal(q, pb) {
  const per = q.area * pb.perHa + pb.mob + (q.opts.ortho ? q.area * pb.ortho : 0) + (q.opts.model3d ? q.area * pb.model3d : 0) + (q.opts.warranty ? pb.warranty : 0);
  const cyc = per * (q.rush ? 1 + pb.rush / 100 : 1);
  return { per: Math.round(cyc), annual: Math.round(cyc) * q.cycles };
}
const cmContractFor = (v, siteId, date) => v.contracts.find(c => c.siteId === siteId && (!date || (date >= c.start && date <= c.end)) && c.status !== "terminated");
const cmPenalty = (sla, wd) => sla && wd > sla.days ? Math.min(sla.cap, (wd - sla.days) * sla.pct) : 0;

/* Provider: one job per flown inspection; invoice at report delivery at the contract price (list price if none) */
function cmJobs(v) {
  const pb = v.pb, I = window.ALL_INSPECTIONS || window.INSPECTIONS || [];
  const asOf = cmT();
  return I.filter(i => (i.progress?.flying || 0) >= 1 && i.startedAt && i.startedAt <= asOf).map(i => {
    const s = cmSite(i.siteId), area = i.area_ha || 100, flights = i.flights || Math.ceil(area / 26);
    const ct = cmContractFor(v, i.siteId, i.startedAt);
    const list = Math.round(pb.mob + area * pb.perHa);
    const amount = ct ? ct.price : list;
    const days = Math.max(1, Math.ceil(flights / 8));
    const cost = Math.round(days * pb.pilotDay + flights * 0.6 * pb.droneHour + area * pb.computeHa + Math.ceil((i.detections || 200) / 60) * pb.reviewHour);
    const reported = (i.progress?.reported || 0) >= 1;
    const repDate = reported ? (() => { const d = i.delivery?.at || cmAdd(i.startedAt, 3 + cmH(i.id) % 6); return d > asOf ? asOf : d; })() : null;
    const wd = repDate ? cmWd(i.startedAt, repDate) : cmWd(i.startedAt, asOf);
    const sla = ct ? ct.sla : { days: 5, coverage: 95, pct: 0, cap: 0 };
    const penPct = reported ? cmPenalty(ct?.sla, wd) : 0;
    const invoice = repDate ? `INV-${i.id.slice(4)}` : null;
    const ov = v.inv[i.id] || {};
    const due = repDate ? cmAdd(repDate, 30) : null;
    const cns = v.cn.filter(c => c.micId === i.id && c.status === "issued");
    const credited = cns.reduce((a, c) => a + c.amount, 0);
    const status = !invoice ? "unbilled" : ov.status || (due < asOf ? (cmH(i.id) % 4 === 0 ? "overdue" : "paid") : "issued");
    return { i, s, client: cmClient(s), area, flights, ct, list, amount, net: amount - credited, credited, cost, margin: amount - credited - cost, days, repDate, wd, sla, slaOk: wd <= sla.days, open: !reported, late: !reported && wd > sla.days,
      penPct, penAmt: Math.round(amount * penPct / 100), penIssued: v.cn.some(c => c.micId === i.id && c.auto), invoice, issued: repDate, due, status };
  }).sort((a, b) => b.i.startedAt.localeCompare(a.i.startedAt));
}

/* Owner: actual spend by site/category from approved+paid supplier invoices (net of penalties) + in-house WO costs */
function cmSInvNet(v, x) { const po = v.pos.find(p => p.id === x.po); const sc = po && v.sContracts.find(c => c.id === po.contract); const pen = cmPenalty(sc?.sla, x.wd || 0); return { po, sc, pen, net: Math.round(x.amount * (1 - pen / 100)) }; }
function cmActuals(v, range) {
  const out = {};
  const add = (site, cat, amt) => { out[site] = out[site] || { inspection: 0, cleaning: 0, vegetation: 0, repairs: 0 }; out[site][cat] = (out[site][cat] || 0) + amt; };
  v.sInv.filter(x => ["approved", "paid"].includes(x.status) && cmIn(x.date, range)).forEach(x => { const { po, net } = cmSInvNet(v, x); add(x.siteId || po?.siteId || "—", x.cat || po?.cat || "repairs", net); });
  (window.woStore?.list || []).filter(w => ["done", "verified"].includes(w.status) && w.cost && !/(Clean|Verde|AeroScan)/.test(w.assignee || "") && cmIn(w.created, range)).forEach(w => add(w.siteId, w.type === "Cleaning" ? "cleaning" : w.type === "Vegetation" ? "vegetation" : "repairs", +w.cost));
  return out;
}

/* PDFs (single page) */
function cmPdf(kind, o) {
  const issuer = kind === "po" ? (window.skyTenant ? window.skyTenant() : "Iberdrola Renovables") : "AeroScan Ibérica · drone inspection services";
  const L = [[`${{ quote: "QUOTE", invoice: "INVOICE", cn: "CREDIT NOTE", po: "PURCHASE ORDER" }[kind]}  ${o.id}`, 18, true], [issuer, 9], [`Date ${o.date || cmT()}${o.due ? "  -  Due " + o.due : ""}${o.valid ? "  -  Valid until " + o.valid : ""}`, 9], ["", 8], [`To: ${o.to}`, 11, true], o.site ? [`Site: ${o.site}`, 10] : ["", 2], ["", 6]];
  (o.lines || []).forEach(([d, a]) => L.push([`${d}${a != null ? "   " + cmEur(a) : ""}`, 10]));
  L.push(["", 6], [`TOTAL   ${cmEur(o.total)}`, 13, true]);
  if (o.note) L.push(["", 6], [o.note, 9]);
  L.push(["", 10], ["Generated by SkyOps", 8]);
  window.skyDownload(`${o.id}.pdf`, window.skyPdf(L));
  window.skyToast(`${o.id} PDF downloaded`);
}
const cmNextId = (prefix, list) => { const n = Math.max(0, ...list.map(x => +(String(x.id).match(/(\d+)$/) || [0, 0])[1])) + 1; return `${prefix}${String(n).padStart(3, "0")}`; };

window.__cmJobs = () => cmJobs(cmStore.v);
Object.assign(window, { cmT, CM_PB_DEFAULT, CM_CATS, CM_PERIODS, cmClient, cmEur, cmAdd, cmWd, cmSite, cmStore, useCM, cmRange, cmIn, cmPeriodLabel, cmQuoteTotal, cmContractFor, cmPenalty, cmJobs, cmSInvNet, cmActuals, cmPdf, cmNextId });
