/* SkyOps — Commercial: price book, quotes, contracts & SLA, invoicing with job margin, crew utilization.
   Invoices + SLA are derived from the inspection record so numbers stay consistent with operations. */

const { useState: uSCM, useEffect: uECM, useMemo: uMCM } = React;

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.owner || CM_CLIENTS[s.id] || "Iberdrola Renovables";
const cmEur = (n) => "€" + Math.round(n).toLocaleString("en-US");
const cmAdd = (d, n) => { const t = new Date(d + "T00:00:00Z"); t.setUTCDate(t.getUTCDate() + n); return t.toISOString().slice(0, 10); };
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 s) h = (h * 31 + c.charCodeAt(0)) % 9973; return h; };

const cmStore = {
  v: (() => { try { const s = localStorage.getItem("skyops_cm_v1"); if (s) return JSON.parse(s); } catch {} 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: "2026-10-08", valid: "2026-11-07" },
    { 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: "2026-09-21", valid: "2026-10-21" },
    { id: "Q-2026-027", client: "Solaria Energía", siteId: "es-se-04", area: 114.5, cycles: 4, opts: {}, rush: true, status: "declined", date: "2026-09-02", valid: "2026-10-02" },
  ], paid: {} }; })(),
  subs: new Set(),
  set(fn) { this.v = fn(this.v); try { localStorage.setItem("skyops_cm_v1", JSON.stringify(this.v)); } catch {} this.subs.forEach(s => s(this.v)); },
};
function useCM() { const [v, setV] = uSCM(cmStore.v); uECM(() => { cmStore.subs.add(setV); return () => cmStore.subs.delete(setV); }, []); return v; }

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: cyc, annual: cyc * q.cycles };
}

/* "As of" date: latest operational activity in the record (+2 days), so open jobs are measured in the data's own time */
function cmAsOf() {
  const ds = (window.INSPECTIONS || []).flatMap(i => [(i.progress?.flying || 0) > 0 ? i.startedAt : null]).filter(Boolean).sort();
  return ds.length ? cmAdd(ds[ds.length - 1], 2) : "2026-04-17";
}

/* One invoice per inspection that reached Report; cost model from crew + drone + compute */
function cmJobs(pb, paid) {
  const asOf = cmAsOf();
  const I = window.INSPECTIONS || [], S = window.SITES || [];
  return I.filter(i => (i.progress?.flying || 0) >= 1 && i.startedAt).map(i => {
    const s = S.find(x => x.id === i.siteId) || {};
    const area = i.area_ha || 100, flights = i.flights || Math.ceil(area / 26);
    const revenue = pb.mob + area * pb.perHa;
    const days = Math.max(1, Math.ceil(flights / 8));
    const cost = 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 ? cmAdd(i.startedAt, 3 + cmH(i.id) % 6) : null;
    const wd = repDate ? cmWd(i.startedAt, repDate) : cmWd(i.startedAt, asOf);
    const issued = repDate || null;
    const due = issued ? cmAdd(issued, 30) : null;
    const status = !issued ? "unbilled" : paid[i.id] || (due < asOf ? (cmH(i.id) % 4 === 0 ? "overdue" : "paid") : "issued");
    return { i, s, client: cmClient(s), area, flights, revenue, cost, margin: revenue - cost, days, repDate, wd, slaOk: wd <= 5, open: !reported, late: !reported && wd > 5, invoice: issued ? `INV-${i.id.slice(4)}` : null, issued, due, status };
  }).sort((a, b) => b.i.startedAt.localeCompare(a.i.startedAt));
}

function CommercialPage({ go, setActiveSite, setActiveMic, setWsStage }) {
  const v = useCM();
  const [tab, setTab] = uSCM("overview");
  const [quoting, setQuoting] = uSCM(false);
  const jobs = uMCM(() => cmJobs(v.pb, v.paid), [v]);
  const billed = jobs.filter(j => j.invoice);
  const rev = billed.reduce((a, j) => a + j.revenue, 0), cost = billed.reduce((a, j) => a + j.cost, 0);
  const ar = billed.filter(j => j.status !== "paid").reduce((a, j) => a + j.revenue, 0);
  const overdue = billed.filter(j => j.status === "overdue");
  const sla = jobs.filter(j => !j.open || j.late), slaPct = sla.length ? sla.filter(j => j.slaOk).length / sla.length * 100 : 100;
  const pipe = v.quotes.filter(q => q.status === "sent").reduce((a, q) => a + cmQuoteTotal(q, v.pb).annual, 0);
  const openJob = (j) => { setActiveSite(j.i.siteId); setActiveMic(j.i.id); setWsStage("report"); go("workspace"); };

  const bySite = Object.values(billed.reduce((m, j) => { const k = j.s.id; m[k] = m[k] || { s: j.s, rev: 0, cost: 0, n: 0 }; m[k].rev += j.revenue; m[k].cost += j.cost; m[k].n++; return m; }, {})).sort((a, b) => b.rev - a.rev);
  const people = (window.resStore?.data.people || []).filter(p => p.skills.includes("Pilot"));
  const util = people.map(p => { const d = jobs.filter(j => j.i.lead === p.name).reduce((a, j) => a + j.days, 0) + (window.woStore?.list || []).filter(w => (w.assignee || "").startsWith(p.name)).length; return { p, d, pct: Math.min(100, d / 20 * 100) }; });

  return (
    <div className="page cm">
      <div className="page-head">
        <div className="title"><div className="kicker">Business · service company</div><h1>Commercial</h1>
          <div className="sub">Quotes, contracts, invoicing and SLA — every invoice is generated from a completed inspection.</div></div>
        <div className="actions"><button className="btn btn-primary" onClick={() => setQuoting(true)}><Icon name="plus" size={12}/>New quote</button></div>
      </div>

      <div className="wo-kpis cm-kpis">
        <div className="wo-kpi"><span className="caps">Invoiced</span><b className="mono">{cmEur(rev)}</b><em>{billed.length} inspections</em></div>
        <div className="wo-kpi is-ok"><span className="caps">Gross margin</span><b className="mono">{rev ? ((rev - cost) / rev * 100).toFixed(1) : "0.0"}%</b><em>{cmEur(rev - cost)} on jobs</em></div>
        <div className={`wo-kpi ${overdue.length ? "is-crit" : ""}`}><span className="caps">Receivables</span><b className="mono">{cmEur(ar)}</b><em>{overdue.length} overdue · {cmEur(overdue.reduce((a, j) => a + j.revenue, 0))}</em></div>
        <div className={`wo-kpi ${slaPct < 90 ? "is-crit" : "is-ok"}`}><span className="caps">SLA · report ≤ 5 wd</span><b className="mono">{slaPct.toFixed(1)}%</b><em>{sla.filter(j => !j.slaOk).length} breaches · {jobs.filter(j => j.open && !j.late).length} on track · as of {cmAsOf()}</em></div>
        <div className="wo-kpi"><span className="caps">Quote pipeline</span><b className="mono">{cmEur(pipe)}</b><em>{v.quotes.filter(q => q.status === "sent").length} sent · annual value</em></div>
      </div>

      {window.CoreStrip && <window.CoreStrip dom="commercial" title="Argus · pricing & margin"/>}
      <div className="tabs" style={{ marginBottom: 12 }}>
        {[["overview", "Overview"], ["quotes", `Quotes · ${v.quotes.length}`], ["invoices", `Invoices · ${billed.length}`], ["sla", "SLA"], ["util", "Utilization"], ["prices", "Price book"]].map(([k, l]) => <button key={k} className={tab === k ? "on" : ""} onClick={() => setTab(k)}>{l}</button>)}
      </div>

      {tab === "overview" && <div className="cm-2">
        <div className="card"><div className="card-hd">Margin by site<span className="sub">invoiced inspections</span></div>
          <div className="cm-bars">{bySite.map(r => { const m = (r.rev - r.cost) / r.rev * 100; return (
            <div key={r.s.id} className="cm-bar"><span className="cm-bar-n">{r.s.name}<em>{r.n} jobs · {cmClient(r.s)}</em></span>
              <span className="cm-bar-t"><i className="c" style={{ width: (r.cost / bySite[0].rev * 100) + "%" }}/><i className="m" style={{ width: ((r.rev - r.cost) / bySite[0].rev * 100) + "%" }}/></span>
              <b className="mono">{cmEur(r.rev)}</b><b className={`mono ${m < 25 ? "warn" : "ok"}`}>{m.toFixed(1)}%</b></div>); })}</div>
          <div className="cm-lg"><span><i className="c"/>Cost</span><span><i className="m"/>Margin</span></div>
        </div>
        <div className="card"><div className="card-hd">Needs action</div>
          <div className="cm-act">
            {overdue.map(j => <div key={j.i.id} className="cm-act-it crit"><Icon name="alert" size={12}/><span><b>{j.invoice}</b> overdue · {j.client} · due {j.due}</span><b className="mono">{cmEur(j.revenue)}</b></div>)}
            {jobs.filter(j => j.late).map(j => <button key={j.i.id} className="cm-act-it crit" onClick={() => openJob(j)}><Icon name="alert" size={12}/><span><b>{j.i.id}</b> report overdue by {j.wd - 5} wd · {j.s.name}</span><em>SLA breach</em></button>)}
            {sla.filter(j => !j.slaOk && !j.open).map(j => <div key={j.i.id} className="cm-act-it warn"><Icon name="clock" size={12}/><span><b>{j.i.id}</b> report delivered in {j.wd} wd · {j.s.name}</span><em>SLA breach</em></div>)}
            {jobs.filter(j => j.open && !j.late && j.wd > 3).map(j => <button key={j.i.id} className="cm-act-it warn" onClick={() => openJob(j)}><Icon name="clock" size={12}/><span><b>{j.i.id}</b> report due in {Math.max(0, 5 - j.wd)} wd · {j.s.name}</span><Icon name="arrow_right" size={11}/></button>)}
            {overdue.length === 0 && !jobs.some(j => j.late || (!j.slaOk && !j.open) || (j.open && j.wd > 3)) && <div className="sec" style={{ fontSize: 12 }}>Nothing pending.</div>}
          </div>
        </div>
      </div>}

      {tab === "quotes" && <div className="card"><table className="tbl">
        <thead><tr><th>Quote</th><th>Client</th><th>Site</th><th className="r">Area</th><th className="r">Cycles / yr</th><th className="r">Per cycle</th><th className="r">Annual</th><th>Status</th><th>Valid until</th><th></th></tr></thead>
        <tbody>{v.quotes.map(q => { const t = cmQuoteTotal(q, v.pb); const s = (window.SITES || []).find(x => x.id === q.siteId); return (
          <tr key={q.id}><td className="mono"><b>{q.id}</b></td><td>{q.client}</td><td className="sec">{s?.name}</td><td className="r mono">{q.area.toFixed(1)} ha</td><td className="r mono">{q.cycles}</td>
            <td className="r mono">{cmEur(t.per)}</td><td className="r mono"><b>{cmEur(t.annual)}</b></td><td><span className={`cm-st cm-st-${q.status}`}>{q.status}</span></td><td className="mono sec">{q.valid}</td>
            <td>{q.status === "sent" && <div style={{ display: "flex", gap: 4 }}>
              <button className="btn btn-primary btn-xs" onClick={() => { cmStore.set(x => ({ ...x, quotes: x.quotes.map(y => y.id === q.id ? { ...y, status: "accepted" } : y) })); setActiveSite(q.siteId); window.dispatchEvent(new CustomEvent("skyops:new-insp", { detail: { siteId: q.siteId } })); }}>Accept & schedule</button>
              <button className="btn btn-ghost btn-xs" onClick={() => cmStore.set(x => ({ ...x, quotes: x.quotes.map(y => y.id === q.id ? { ...y, status: "declined" } : y) }))}>Declined</button></div>}</td></tr>); })}</tbody></table></div>}

      {tab === "invoices" && <div className="card"><table className="tbl">
        <thead><tr><th>Invoice</th><th>Inspection</th><th>Client · site</th><th className="r">Area</th><th className="r">Amount</th><th className="r">Job cost</th><th className="r">Margin</th><th>Issued</th><th>Due</th><th>Status</th></tr></thead>
        <tbody>{billed.map(j => (
          <tr key={j.i.id}><td className="mono"><b>{j.invoice}</b></td><td className="mono"><button className="cm-link" onClick={() => openJob(j)}>{j.i.id}</button></td><td><b>{j.client}</b><div className="sec" style={{ fontSize: 10.5 }}>{j.s.name}</div></td>
            <td className="r mono">{j.area} ha</td><td className="r mono">{cmEur(j.revenue)}</td><td className="r mono sec">{cmEur(j.cost)}</td><td className={`r mono ${j.margin / j.revenue < .25 ? "warn" : ""}`}>{(j.margin / j.revenue * 100).toFixed(1)}%</td>
            <td className="mono sec">{j.issued}</td><td className="mono sec">{j.due}</td>
            <td>{j.status === "paid" ? <span className="cm-st cm-st-accepted">paid</span> : <div style={{ display: "flex", gap: 4, alignItems: "center" }}><span className={`cm-st cm-st-${j.status}`}>{j.status}</span><button className="btn btn-ghost btn-xs" onClick={() => cmStore.set(x => ({ ...x, paid: { ...x.paid, [j.i.id]: "paid" } }))}>Mark paid</button></div>}</td></tr>))}</tbody></table>
        {jobs.filter(j => !j.invoice).length > 0 && <div className="cm-foot sec">{jobs.filter(j => !j.invoice).length} inspections not yet billable — an invoice is issued when the report is delivered.</div>}
      </div>}

      {tab === "sla" && <div className="card">
        <div className="card-hd">Report delivery SLA<span className="sub">as of {cmAsOf()} · target ≤ 5 working days from flight · critical alert ≤ 24 h · coverage ≥ 95%</span></div>
        <table className="tbl"><thead><tr><th>Inspection</th><th>Site</th><th>Flight</th><th>Report</th><th className="r">Working days</th><th className="r">Coverage</th><th>Result</th></tr></thead>
          <tbody>{jobs.map(j => (
            <tr key={j.i.id} onClick={() => openJob(j)}><td className="mono"><b>{j.i.id}</b></td><td>{j.s.name}</td><td className="mono sec">{j.i.startedAt}</td><td className="mono sec">{j.repDate || "not delivered"}</td>
              <td className="r"><span className="cm-sla"><i style={{ width: Math.min(100, j.wd / 8 * 100) + "%", background: j.wd > 5 ? "var(--sev-critical)" : j.wd > 3 ? "var(--sev-medium)" : "var(--sev-low)" }}/><em style={{ left: "62.5%" }}/></span><b className="mono">{j.wd}</b></td>
              <td className={`r mono ${j.i.coverage && j.i.coverage < 95 ? "warn" : ""}`}>{j.i.coverage ? j.i.coverage.toFixed(1) + "%" : "—"}</td>
              <td>{j.late ? <span className="cm-st cm-st-overdue">breach · running</span> : j.open ? <span className="cm-st cm-st-sent">on track</span> : j.slaOk ? <span className="cm-st cm-st-accepted">met</span> : <span className="cm-st cm-st-overdue">breach</span>}</td></tr>))}</tbody></table>
      </div>}

      {tab === "util" && <div className="card"><div className="card-hd">Pilot utilization<span className="sub">billable days in the period · target 20 days / month</span></div>
        <div className="cm-bars">{util.map(({ p, d, pct }) => (
          <div key={p.id} className="cm-bar"><span className="cm-bar-n">{p.name}<em>{window.RES_KIND[p.kind]} · {p.org}</em></span>
            <span className="cm-bar-t"><i className="m" style={{ width: pct + "%", background: pct > 90 ? "var(--sev-medium)" : undefined }}/></span>
            <b className="mono">{d} d</b><b className={`mono ${pct < 40 ? "warn" : "ok"}`}>{pct.toFixed(0)}%</b></div>))}</div>
      </div>}

      {tab === "prices" && <div className="card st-card">
        <div className="st-card-h">Price book<span>Used by new quotes and invoices</span></div>
        {[["perHa", "Thermographic inspection", "€ / ha"], ["mob", "Mobilisation", "€ / cycle"], ["refly", "Re-fly", "€ / sortie"], ["rush", "Rush delivery (48 h)", "% surcharge"], ["ortho", "RGB orthomosaic", "€ / ha"], ["model3d", "3D model / point cloud", "€ / ha"], ["warranty", "Warranty claim pack", "€ / claim"]].map(([k, l, u]) => (
          <div key={k} className="st-row"><div className="st-row-l"><b>{l}</b><span>{u}</span></div><div className="st-row-c"><input className="inp mono st-num" type="number" value={v.pb[k]} onChange={e => cmStore.set(x => ({ ...x, pb: { ...x.pb, [k]: +e.target.value } }))}/></div></div>))}
        <div className="st-sub">Cost model</div>
        {[["pilotDay", "Pilot day", "€ / day"], ["droneHour", "Drone operating cost", "€ / flight hour"], ["computeHa", "Processing compute", "€ / ha"], ["reviewHour", "Analyst review", "€ / hour"]].map(([k, l, u]) => (
          <div key={k} className="st-row"><div className="st-row-l"><b>{l}</b><span>{u}</span></div><div className="st-row-c"><input className="inp mono st-num" type="number" value={v.pb[k]} onChange={e => cmStore.set(x => ({ ...x, pb: { ...x.pb, [k]: +e.target.value } }))}/></div></div>))}
      </div>}

      {quoting && <CmQuoteModal pb={v.pb} onClose={() => setQuoting(false)}/>}
    </div>
  );
}

function CmQuoteModal({ pb, onClose }) {
  const sites = window.SITES || [];
  const [q, setQ] = uSCM(() => { const s = sites[0]; return { siteId: s.id, client: cmClient(s), area: window.siteProfile ? +window.siteProfile(s).area.toFixed(1) : 100, cycles: 4, opts: { ortho: false, model3d: false, warranty: false }, rush: false }; });
  const pickSite = (id) => { const s = sites.find(x => x.id === id); setQ(x => ({ ...x, siteId: id, client: cmClient(s), area: window.siteProfile ? +window.siteProfile(s).area.toFixed(1) : x.area })); };
  const t = cmQuoteTotal(q, pb);
  const save = () => { cmStore.set(x => ({ ...x, quotes: [{ ...q, id: `Q-2026-${String(32 + x.quotes.length).padStart(3, "0")}`, status: "sent", date: window.SKY_TODAY, valid: window.skyAdd(30) }, ...x.quotes] })); onClose(); };
  return (
    <div className="ni-scrim" onClick={onClose}><div className="ni-modal" onClick={e => e.stopPropagation()} role="dialog">
      <div className="ni-hd"><div><div className="kicker">Commercial</div><h3>New quote</h3></div><button className="ask-hd-btn" onClick={onClose}><Icon name="x" size={13}/></button></div>
      <div className="ni-body">
        <div className="ni-grid">
          <label className="field"><span>Site</span><select className="sel" value={q.siteId} onChange={e => pickSite(e.target.value)}>{sites.map(s => <option key={s.id} value={s.id}>{s.name}</option>)}</select></label>
          <label className="field"><span>Client</span><input className="inp" value={q.client} onChange={e => setQ(x => ({ ...x, client: e.target.value }))}/></label>
          <label className="field"><span>Area (ha) · from site setup</span><input className="inp mono" type="number" value={q.area} onChange={e => setQ(x => ({ ...x, area: +e.target.value }))}/></label>
          <label className="field"><span>Inspections per year</span><select className="sel" value={q.cycles} onChange={e => setQ(x => ({ ...x, cycles: +e.target.value }))}>{[[12, "Monthly"], [4, "Quarterly"], [2, "Semi-annual"], [1, "Annual"]].map(([n, l]) => <option key={n} value={n}>{l} · {n}</option>)}</select></label>
          <div className="field ni-span"><span>Options</span><div className="fl-skills fl-skills-pick">
            {[["ortho", `RGB orthomosaic · €${pb.ortho}/ha`], ["model3d", `3D model · €${pb.model3d}/ha`], ["warranty", `Warranty claim pack · €${pb.warranty}`]].map(([k, l]) => <button key={k} className={q.opts[k] ? "on" : ""} onClick={() => setQ(x => ({ ...x, opts: { ...x.opts, [k]: !x.opts[k] } }))}>{l}</button>)}
            <button className={q.rush ? "on" : ""} onClick={() => setQ(x => ({ ...x, rush: !x.rush }))}>Rush 48 h · +{pb.rush}%</button></div></div>
        </div>
        <div className="wc-sum">
          <div className="ps-kv"><span>Inspection · {q.area} ha × €{pb.perHa}</span><b className="mono">{cmEur(q.area * pb.perHa)}</b></div>
          <div className="ps-kv"><span>Mobilisation</span><b className="mono">{cmEur(pb.mob)}</b></div>
          <div className="ps-kv"><span>Per cycle</span><b className="mono">{cmEur(t.per)}</b></div>
          <div className="ps-kv wc-tot"><span>Annual contract value</span><b className="mono">{cmEur(t.annual)}</b></div>
        </div>
      </div>
      <div className="ni-ft"><span className="grow"/><button className="btn btn-ghost" onClick={onClose}>Cancel</button><button className="btn btn-primary" onClick={save}><Icon name="send" size={12}/>Send quote</button></div>
    </div></div>
  );
}

window.__cmJobs = () => cmJobs(cmStore.v.pb, cmStore.v.paid);
Object.assign(window, { CommercialPage, cmStore });
