/* SkyOps — Site setup editor: boundary & zones (drag vertices, add/remove, exclusions, re-zone),
   asset registry (inverter → combiner → string hierarchy, SCADA tags, module serial lookup), details, change history.
   Saved as a new site version; committed inspections keep their zones, new ones use the new layout. */

const { useState: uSSC, useMemo: uMSC, useRef: uRSC } = React;

const scPolyArea = (p) => Math.abs(p.reduce((a, q, i) => { const r = p[(i + 1) % p.length]; return a + q[0] * r[1] - r[0] * q[1]; }, 0) / 2);
const scInPoly = (x, y, p) => { let c = false; for (let i = 0, j = p.length - 1; i < p.length; j = i++) { const [xi, yi] = p[i], [xj, yj] = p[j]; if ((yi > y) !== (yj > y) && x < (xj - xi) * (y - yi) / (yj - yi) + xi) c = !c; } return c; };
function scZones(poly, excl, n, orient, areaHa, haPerSortie) {
  if (poly.length < 3) return [];
  const xs = poly.map(q => q[0]), ys = poly.map(q => q[1]); const x0 = Math.min(...xs), y0 = Math.min(...ys), x1 = Math.max(...xs), y1 = Math.max(...ys);
  const st = 4, pts = [];
  for (let x = x0 + 2; x < x1; x += st) for (let y = y0 + 2; y < y1; y += st) if (scInPoly(x, y, poly) && !(excl.length >= 3 && scInPoly(x, y, excl))) pts.push([x, y]);
  if (!pts.length) return [];
  const cuts = (arr, k, m) => { const s = [...arr].sort((a, b) => a[k] - b[k]); const o = []; for (let i = 1; i < m; i++) o.push(s[Math.floor(s.length * i / m)][k]); return o; };
  const cols = orient === "cols" ? n : Math.max(1, Math.round(Math.sqrt(n * (x1 - x0) / (y1 - y0))));
  const xc = cuts(pts, 0, cols), out = [];
  for (let c = 0; c < cols; c++) {
    const cp = pts.filter(p => xc.filter(q => p[0] >= q).length === c);
    const rows = orient === "cols" ? 1 : Math.max(1, Math.round(n / cols) + (c < n - cols * Math.round(n / cols) ? 1 : 0));
    const yc = cuts(cp, 1, rows), xa = c === 0 ? x0 : xc[c - 1], xb = c === cols - 1 ? x1 : xc[c];
    for (let r = 0; r < rows; r++) {
      const zp = cp.filter(p => yc.filter(q => p[1] >= q).length === r); if (!zp.length) continue;
      const ya = r === 0 ? y0 : yc[r - 1], yb = r === rows - 1 ? y1 : yc[r];
      const ha = areaHa * zp.length / pts.length, i = out.length;
      out.push({ id: `Z${String(i + 1).padStart(2, "0")}`, x: xa, y: ya, w: xb - xa, h: yb - ya, cx: zp.reduce((a, p) => a + p[0], 0) / zp.length, cy: zp.reduce((a, p) => a + p[1], 0) / zp.length, ha: +ha.toFixed(1), sorties: Math.max(1, Math.ceil(ha / haPerSortie)), hue: (i * 47) % 360 });
    }
  }
  return out;
}
function scDefaultAssets(site, prof) {
  const nInv = prof.inverters, strings = prof.strings, cmb = 8, boxes = nInv * cmb, base = Math.floor(strings / boxes), extra = strings % boxes;
  return Array.from({ length: nInv }).map((_, i) => ({ id: `INV-${String(i + 1).padStart(2, "0")}`, model: prof.invModel, kw: Math.round(prof.mwac * 1000 / nInv), scada: `${site.id.toUpperCase()}.INV${String(i + 1).padStart(2, "0")}`, status: "in service",
    combiners: Array.from({ length: cmb }).map((_, j) => ({ id: `CMB-${String(j + 1).padStart(2, "0")}`, strings: base + (i * cmb + j < extra ? 1 : 0), scada: `${site.id.toUpperCase()}.INV${String(i + 1).padStart(2, "0")}.CB${String(j + 1).padStart(2, "0")}` })) }));
}
function scSave(site, patch, note) {
  const v = (site.version || 1) + 1;
  const hist = [{ v, at: new Date().toISOString().slice(0, 16).replace("T", " "), by: "You", note }, ...(site.history || [{ v: 1, at: site.cod || "2026-01-01", by: "Import", note: "Site created" }])];
  Object.assign(site, patch, { version: v, history: hist });
  try {
    const ns = JSON.parse(localStorage.getItem("skyops_new_sites") || "[]"); const i = ns.findIndex(x => x.id === site.id);
    if (i >= 0) { ns[i] = { ...ns[i], ...patch, version: v, history: hist }; localStorage.setItem("skyops_new_sites", JSON.stringify(ns)); }
    else { const ed = JSON.parse(localStorage.getItem("skyops_site_edits") || "{}"); ed[site.id] = { ...(ed[site.id] || {}), ...patch, version: v, history: hist }; localStorage.setItem("skyops_site_edits", JSON.stringify(ed)); }
  } catch {}
}

function SiteConfigPage({ go, site }) {
  site = site || (window.SITES || []).find(s => s.id === localStorage.getItem("skyops_site")) || (window.SITES || [])[0];
  const prof = window.siteProfile(site);
  const [tab, setTab] = uSSC("boundary");
  const [, force] = uSSC(0);
  const committed = (window.INSPECTIONS || []).filter(i => i.siteId === site.id && i.zones && (i.progress?.closed || 0) < 1);
  return (
    <div className="page sc">
      <div className="page-head">
        <div className="title"><div className="kicker"><button className="cm-link" onClick={() => go("site")}>{site.name}</button> · site setup · <span className="mono">v{site.version || 1}</span></div><h1>Edit site</h1>
          <div className="sub">Boundary, zones, asset registry and plant details. Each save creates a new site version.</div></div>
        <div className="actions"><button className="btn btn-secondary" onClick={() => go("site")}>Back to site</button></div>
      </div>
      <div className="tabs" style={{ marginBottom: 12 }}>{[["boundary", "Boundary & zones"], ["assets", "Asset registry"], ["details", "Details"], ["history", `History · ${(site.history || []).length || 1}`]].map(([k, l]) => <button key={k} className={tab === k ? "on" : ""} onClick={() => setTab(k)}>{l}</button>)}</div>
      {tab === "boundary" && <ScBoundary site={site} prof={prof} committed={committed} saved={() => force(x => x + 1)}/>}
      {tab === "assets" && <ScAssets site={site} prof={prof} saved={() => force(x => x + 1)}/>}
      {tab === "details" && <ScDetails site={site} prof={prof} saved={() => force(x => x + 1)}/>}
      {tab === "history" && <div className="card"><table className="tbl"><thead><tr><th>Version</th><th>Date</th><th>By</th><th>Change</th></tr></thead>
        <tbody>{(site.history || [{ v: 1, at: prof.cod, by: "Import", note: "Site created" }]).map(h => <tr key={h.v}><td className="mono"><b>v{h.v}</b></td><td className="mono sec">{h.at}</td><td>{h.by}</td><td>{h.note}</td></tr>)}</tbody></table></div>}
    </div>
  );
}

function ScBoundary({ site, prof, committed, saved }) {
  const W = window.NS;
  const [poly, setPoly] = uSSC(() => prof.poly.map(p => [...p]));
  const [excl, setExcl] = uSSC(() => (prof.excl || []).map(p => [...p]));
  const [layer, setLayer] = uSSC("fence");
  const [drag, setDrag] = uSSC(null);
  const [n, setN] = uSSC(prof.zones.length);
  const [orient, setOrient] = uSSC("cols");
  const [dirty, setDirty] = uSSC(false);
  const svg = uRSC(null);
  // metres per unit: keep the site's registered area for the current drawing
  const mpu = site.mpu || Math.sqrt(prof.area * 1e4 / Math.max(1, scPolyArea(prof.poly) - (prof.excl && prof.excl.length >= 3 ? scPolyArea(prof.excl) : 0)));
  const areaHa = (scPolyArea(poly) - (excl.length >= 3 ? scPolyArea(excl) : 0)) * mpu * mpu / 1e4;
  const zones = uMSC(() => scZones(poly, excl, n, orient, areaHa, prof.haPerSortie), [poly, excl, n, orient, areaHa]);
  const toSvg = (e) => { const s = svg.current, p = s.createSVGPoint(); p.x = e.clientX; p.y = e.clientY; const r = p.matrixTransform(s.getScreenCTM().inverse()); return [Math.round(r.x), Math.round(r.y)]; };
  const cur = layer === "fence" ? poly : excl, setCur = layer === "fence" ? setPoly : setExcl;
  const onMove = (e) => { if (!drag) return; const pt = toSvg(e); (drag.l === "fence" ? setPoly : setExcl)(p => p.map((q, i) => i === drag.i ? pt : q)); setDirty(true); };
  const insert = (i) => { setCur(p => { const a = p[i], b = p[(i + 1) % p.length]; const c = [...p]; c.splice(i + 1, 0, [Math.round((a[0] + b[0]) / 2), Math.round((a[1] + b[1]) / 2)]); return c; }); setDirty(true); };
  const remove = (i) => { if (cur.length <= 3) return; setCur(p => p.filter((_, j) => j !== i)); setDirty(true); };
  const addExcl = () => { const [cx, cy] = [zones[0]?.cx || 400, zones[0]?.cy || 280]; setExcl([[cx - 30, cy - 25], [cx + 30, cy - 25], [cx + 30, cy + 25], [cx - 30, cy + 25]]); setLayer("excl"); setDirty(true); };
  const save = () => {
    scSave(site, { poly, excl, mpu, area_ha: +areaHa.toFixed(1), zones: zones.map(({ hue, ...z }) => z) }, `Boundary ${areaHa.toFixed(1)} ha (was ${prof.area.toFixed(1)}) · ${zones.length} zones`);
    setDirty(false); saved();
  };
  const changed = Math.abs(areaHa - prof.area) > 0.05 || zones.length !== prof.zones.length;
  return (
    <div className="sc-b">
      <div className="ns-map sc-map">
        <svg ref={svg} viewBox="0 0 940 560" preserveAspectRatio="xMidYMid meet" onPointerMove={onMove} onPointerUp={() => setDrag(null)} onPointerLeave={() => setDrag(null)}>
          <defs><pattern id="sc-h" width="7" height="7" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><line x1="0" y1="0" x2="0" y2="7" stroke="#ef4444" strokeWidth="1.3" opacity=".8"/></pattern>
            <clipPath id="sc-clip"><polygon points={poly.map(p => p.join(",")).join(" ")}/></clipPath></defs>
          <rect width="940" height="560" fill="#2b2f22"/>
          <g clipPath="url(#sc-clip)">{Array.from({ length: 44 }).map((_, i) => <rect key={i} x="0" y={40 + i * 11} width="940" height="6" fill="#1c2f48" opacity=".85"/>)}</g>
          <g clipPath="url(#sc-clip)">{zones.map(z => <rect key={z.id} x={z.x} y={z.y} width={z.w} height={z.h} fill={`hsla(${z.hue},70%,55%,.16)`} stroke={`hsl(${z.hue},70%,62%)`} strokeWidth="1.4" strokeDasharray="5 3"/>)}</g>
          <polygon points={poly.map(p => p.join(",")).join(" ")} fill="none" stroke="#60a5fa" strokeWidth={layer === "fence" ? 2.4 : 1.4} opacity={layer === "fence" ? 1 : .6}/>
          {excl.length >= 3 && <polygon points={excl.map(p => p.join(",")).join(" ")} fill="url(#sc-h)" stroke="#ef4444" strokeWidth={layer === "excl" ? 2.2 : 1.2}/>}
          {zones.map(z => <text key={z.id} x={z.cx} y={z.cy + 5} textAnchor="middle" fontSize="14" fontWeight="700" fill="#f8fafc" fontFamily="var(--font-mono)" pointerEvents="none" style={{ paintOrder: "stroke", stroke: "#0a0f1a", strokeWidth: 3 }}>{z.id}</text>)}
          {cur.map((p, i) => { const q = cur[(i + 1) % cur.length]; return <circle key={"m" + i} cx={(p[0] + q[0]) / 2} cy={(p[1] + q[1]) / 2} r="4" fill="#0a0f1a" stroke={layer === "fence" ? "#60a5fa" : "#ef4444"} strokeDasharray="2 2" style={{ cursor: "copy" }} onClick={() => insert(i)}><title>Add vertex</title></circle>; })}
          {cur.map((p, i) => <circle key={i} cx={p[0]} cy={p[1]} r="6" fill={layer === "fence" ? "#60a5fa" : "#ef4444"} stroke="#0a0f1a" strokeWidth="1.5" style={{ cursor: "grab" }}
            onPointerDown={(e) => { e.preventDefault(); setDrag({ l: layer, i }); }} onDoubleClick={() => remove(i)} onContextMenu={(e) => { e.preventDefault(); remove(i); }}><title>Drag to move · double-click to remove</title></circle>)}
        </svg>
        <div className="ns-map-badge mono">{areaHa.toFixed(1)} ha · {zones.length} zones</div>
        <div className="sc-hint">Drag a vertex to move · click a midpoint to add · double-click a vertex to remove</div>
      </div>
      <div className="card sc-side">
        <div className="card-hd">Edit</div>
        <div className="sc-side-b">
          <div className="field"><span className="caps">Layer</span><div className="ps-seg"><button className={layer === "fence" ? "on" : ""} onClick={() => setLayer("fence")}>Fence line</button><button className={layer === "excl" ? "on" : ""} onClick={() => excl.length >= 3 ? setLayer("excl") : addExcl()}>{excl.length >= 3 ? "Exclusion" : "+ Exclusion"}</button></div></div>
          {excl.length >= 3 && <button className="btn btn-ghost btn-xs" onClick={() => { setExcl([]); setLayer("fence"); setDirty(true); }}>Remove exclusion</button>}
          <div className="field"><span className="caps">Zones · {n}</span><input type="range" min="1" max="16" value={n} onChange={e => { setN(+e.target.value); setDirty(true); }}/></div>
          <div className="field"><span className="caps">Layout</span><div className="ps-seg">{[["cols", "Strips"], ["grid", "Grid"]].map(([k, l]) => <button key={k} className={orient === k ? "on" : ""} onClick={() => { setOrient(k); setDirty(true); }}>{l}</button>)}</div></div>
          <div className="sc-diff">
            <div className="ps-kv"><span>Fenced area</span><b className="mono">{prof.area.toFixed(1)} → <span className={changed ? "acc" : ""}>{areaHa.toFixed(1)} ha</span></b></div>
            <div className="ps-kv"><span>Zones</span><b className="mono">{prof.zones.length} → <span className={changed ? "acc" : ""}>{zones.length}</span></b></div>
            <div className="ps-kv"><span>Sorties / cycle</span><b className="mono">{prof.zones.reduce((a, z) => a + z.sorties, 0)} → {zones.reduce((a, z) => a + z.sorties, 0)}</b></div>
          </div>
          <div className="ns-zlist">{zones.map(z => <div key={z.id} className="ns-z"><i style={{ background: `hsl(${z.hue},70%,62%)` }}/><b className="mono">{z.id}</b><span className="mono sec">{z.ha} ha</span><span className="mono sec">{z.sorties} sorties</span></div>)}</div>
          {committed.length > 0 && dirty && <div className="fl-alert" style={{ margin: 0 }}><Icon name="alert" size={12}/><span>{committed.map(i => i.id).join(", ")} already committed — {committed.length === 1 ? "it keeps" : "they keep"} the current zones. New inspections use the new layout.</span></div>}
          <div style={{ display: "flex", gap: 6 }}>
            <button className="btn btn-ghost btn-xs" disabled={!dirty} onClick={() => { setPoly(prof.poly.map(p => [...p])); setExcl((prof.excl || []).map(p => [...p])); setN(prof.zones.length); setDirty(false); }}>Discard</button>
            <span className="grow"/>
            <button className="btn btn-primary btn-xs" disabled={!dirty} onClick={save}><Icon name="check" size={11}/>Save as v{(site.version || 1) + 1}</button>
          </div>
        </div>
      </div>
    </div>
  );
}

function ScAssets({ site, prof, saved }) {
  const [rows, setRows] = uSSC(() => site.assets || scDefaultAssets(site, prof));
  const [open, setOpen] = uSSC(() => new Set(["INV-01"]));
  const [dirty, setDirty] = uSSC(false);
  const [q, setQ] = uSSC("");
  const upd = (i, p) => { setRows(r => r.map((x, j) => j === i ? { ...x, ...p } : x)); setDirty(true); };
  const updC = (i, k, p) => { setRows(r => r.map((x, j) => j === i ? { ...x, combiners: x.combiners.map((c, m) => m === k ? { ...c, ...p } : c) } : x)); setDirty(true); };
  const strings = rows.reduce((a, r) => a + r.combiners.reduce((b, c) => b + (+c.strings || 0), 0), 0);
  const kw = rows.filter(r => r.status === "in service").reduce((a, r) => a + (+r.kw || 0), 0);
  const perStr = Math.round(prof.modules / Math.max(1, prof.strings));
  const lookup = uMSC(() => {
    if (q.length < 4) return null;
    const h = [...q].reduce((a, c) => (a * 31 + c.charCodeAt(0)) % 99991, 7);
    const inv = rows[h % rows.length], cmb = inv.combiners[h % inv.combiners.length];
    return { inv: inv.id, cmb: cmb.id, str: `STR-${String(1 + h % Math.max(1, cmb.strings)).padStart(2, "0")}`, pos: 1 + h % perStr, zone: (prof.zones[h % prof.zones.length] || {}).id };
  }, [q, rows]);
  const addInv = () => { const i = rows.length + 1; setRows(r => [...r, { id: `INV-${String(i).padStart(2, "0")}`, model: prof.invModel, kw: r[0]?.kw || 4600, scada: `${site.id.toUpperCase()}.INV${String(i).padStart(2, "0")}`, status: "in service", combiners: [{ id: "CMB-01", strings: 20, scada: "" }] }]); setOpen(o => new Set([...o, `INV-${String(i).padStart(2, "0")}`])); setDirty(true); };
  return (<>
    <div className="wo-kpis">
      <div className="wo-kpi"><span className="caps">Inverters</span><b className="mono">{rows.filter(r => r.status === "in service").length}</b><em>{rows.length} registered · {(kw / 1000).toFixed(1)} MWac in service</em></div>
      <div className={`wo-kpi ${Math.abs(strings - prof.strings) > prof.strings * 0.02 ? "is-crit" : ""}`}><span className="caps">Strings</span><b className="mono">{strings.toLocaleString()}</b><em>plant record {(+prof.strings).toLocaleString()}{Math.abs(strings - prof.strings) > prof.strings * 0.02 ? " · mismatch" : " · reconciled"}</em></div>
      <div className="wo-kpi"><span className="caps">Modules</span><b className="mono">{(strings === +prof.strings ? +prof.modules : strings * perStr).toLocaleString()}</b><em>≈ {perStr} per string · {prof.model.split(" ").slice(0, 2).join(" ")}</em></div>
      <div className="wo-kpi"><span className="caps">SCADA tags</span><b className="mono">{rows.reduce((a, r) => a + 1 + r.combiners.filter(c => c.scada).length, 0)}</b><em>{rows.reduce((a, r) => a + r.combiners.filter(c => !c.scada).length, 0)} combiners unmapped</em></div>
    </div>
    <div className="wo-tb">
      <div className="lg-search wo-search" style={{ maxWidth: 320 }}><Icon name="search" size={11}/><input value={q} onChange={e => setQ(e.target.value)} placeholder="Find module by serial"/></div>
      {lookup && <span className="sc-look mono"><Icon name="crosshair" size={11}/>{lookup.zone} · {lookup.inv} · {lookup.cmb} · {lookup.str} · position {lookup.pos}</span>}
      <span className="grow"/>
      <button className="btn btn-secondary btn-xs"><Icon name="upload" size={11}/>Import CSV / SCADA export</button>
      <button className="btn btn-secondary btn-xs" onClick={addInv}><Icon name="plus" size={11}/>Add inverter</button>
      <button className="btn btn-primary btn-xs" disabled={!dirty} onClick={() => { scSave(site, { assets: rows, inverters: rows.filter(r => r.status === "in service").length, strings }, `Asset registry · ${rows.length} inverters · ${strings} strings`); setDirty(false); saved(); }}><Icon name="check" size={11}/>Save registry</button>
    </div>
    <div className="card"><table className="tbl sc-tbl">
      <thead><tr><th style={{ width: 28 }}></th><th>Asset</th><th>Model</th><th className="r">Rated kW</th><th className="r">Strings</th><th>SCADA tag</th><th>Status</th></tr></thead>
      <tbody>{rows.map((r, i) => [
        <tr key={r.id} className="sc-inv">
          <td><button className="fa-ic sc-tg" onClick={() => setOpen(o => { const s = new Set(o); s.has(r.id) ? s.delete(r.id) : s.add(r.id); return s; })}><Icon name={open.has(r.id) ? "chevron_down" : "chevron_right"} size={12}/></button></td>
          <td className="mono"><b>{r.id}</b><span className="sec"> · {r.combiners.length} combiners</span></td>
          <td><input className="inp sc-in" value={r.model} onChange={e => upd(i, { model: e.target.value })}/></td>
          <td className="r"><input className="inp mono sc-in sc-num" value={r.kw} onChange={e => upd(i, { kw: +e.target.value })}/></td>
          <td className="r mono">{r.combiners.reduce((a, c) => a + (+c.strings || 0), 0)}</td>
          <td><input className="inp mono sc-in" value={r.scada} onChange={e => upd(i, { scada: e.target.value })}/></td>
          <td><select className="sel sc-in" value={r.status} onChange={e => upd(i, { status: e.target.value })}>{["in service", "replaced", "decommissioned"].map(s => <option key={s}>{s}</option>)}</select></td>
        </tr>,
        ...(open.has(r.id) ? r.combiners.map((c, k) => <tr key={r.id + c.id} className="sc-cmb"><td></td><td className="mono">└ {c.id}</td><td className="sec">Combiner box</td><td></td>
          <td className="r"><input className="inp mono sc-in sc-num" value={c.strings} onChange={e => updC(i, k, { strings: +e.target.value })}/></td>
          <td><input className={`inp mono sc-in ${c.scada ? "" : "sc-miss"}`} value={c.scada} placeholder="unmapped" onChange={e => updC(i, k, { scada: e.target.value })}/></td><td></td></tr>) : []),
      ])}</tbody></table></div>
  </>);
}

function ScDetails({ site, prof, saved }) {
  const [f, setF] = uSSC({ name: site.name, capacity_mw: site.capacity_mw, mwac: prof.mwac, model: prof.model, mount: prof.mount, tilt: prof.tilt, azimuth: prof.azimuth, cod: prof.cod, owner: prof.owner, operator: prof.operator, grid: prof.grid, cadence: prof.cadence, lat: prof.lat, lng: prof.lng });
  const set = (k) => (e) => setF(x => ({ ...x, [k]: e.target.value }));
  return (<div className="card st-card" style={{ maxWidth: 820 }}>
    <div className="st-card-h">Plant details<span>Used by planning, warranty and commercial</span></div>
    <div className="sc-grid">
      {[["name", "Site name"], ["capacity_mw", "DC capacity (MWp)"], ["mwac", "AC capacity (MWac)"], ["model", "Module model"], ["mount", "Mounting"], ["tilt", "Tilt"], ["azimuth", "Azimuth"], ["cod", "COD"], ["owner", "Owner"], ["operator", "O&M operator"], ["grid", "Grid connection"], ["cadence", "Inspection cadence"], ["lat", "Latitude"], ["lng", "Longitude"]].map(([k, l]) => (
        <label key={k} className="field"><span>{l}</span><input className="inp" value={f[k]} onChange={set(k)} type={k === "cod" ? "date" : "text"}/></label>))}
    </div>
    <div style={{ display: "flex", padding: "0 18px 16px" }}><span className="grow"/><button className="btn btn-primary btn-xs" onClick={() => { scSave(site, { ...f, capacity_mw: +f.capacity_mw, mwac: +f.mwac }, "Plant details edited"); saved(); }}><Icon name="check" size={11}/>Save details</button></div>
  </div>);
}

Object.assign(window, { SiteConfigPage });
