/* SkyOps — Zone report data model
   One flown zone of a large PV farm: rows → tables → panels (portrait, 2-high).
   Every panel gets a thermal signature (6×12 cell grid) + soiling index. Deterministic. */

function zrBuild(seed0, zoneId) {
  const ROWS = 12, TABLES = 4, TCOLS = 15, TROWS = 2;
  const PER_ROW = TABLES * TCOLS * TROWS;
  const N = ROWS * PER_ROW;
  const CELL_C = 6, CELL_R = 12;
  const AMBIENT = 27.4, IRR = 912, T_BASE = 46.8;
  const MODULE = { wp: 550, model: "Longi Hi-MO5 LR5-72HPH-550M", eur_yr: 53 };

  let s = seed0 % 233280;
  const rnd = () => (s = (s * 9301 + 49297) % 233280) / 233280;
  const OFF = { r: seed0 % 12, t: Math.floor(seed0 / 7) % 4 };
  const PIDP = { r: (OFF.r + 5) % 12, t: (OFF.t + 2) % 4 };
  const rt = (o) => `${zoneId}-R${String(o.r + 1).padStart(2, "0")}-T${o.t + 1}`;

  const idx = (row, table, tr, tc) => row * PER_ROW + table * (TCOLS * TROWS) + tr * TCOLS + tc;
  const loc = (i) => {
    const row = Math.floor(i / PER_ROW), rem = i % PER_ROW;
    const table = Math.floor(rem / (TCOLS * TROWS)), r2 = rem % (TCOLS * TROWS);
    const tr = Math.floor(r2 / TCOLS), tc = r2 % TCOLS;
    return { row, table, tr, tc };
  };
  const panelId = (i) => { const l = loc(i); return `${zoneId}-R${String(l.row + 1).padStart(2, "0")}-T${l.table + 1}-${String(l.tr * TCOLS + l.tc + 1).padStart(2, "0")}`; };
  const stringId = (i) => { const l = loc(i); return `INV-${String(7 + Math.floor(l.row / 4)).padStart(2, "0")}.S${String((l.row % 4) * TABLES + l.table + 1).padStart(2, "0")}`; };

  const panels = [];
  for (let i = 0; i < N; i++) {
    const l = loc(i);
    // soiling: dust gradient from the gravel road on the south-west edge + random bird droppings
    const roadDist = (ROWS - 1 - l.row) * 0.6 + l.table * 0.9 + l.tc * 0.03;
    let soil = Math.max(0, 0.82 - roadDist * 0.16) + (rnd() - 0.5) * 0.12;
    let bird = rnd() < 0.012;
    if (bird) soil = Math.max(soil, 0.55);
    soil = Math.max(0, Math.min(1, soil));
    panels.push({ i, ...l, id: panelId(i), string: stringId(i), soil, bird,
      type: "ok", dT: 0, tmax: 0, tmean: 0, cls: 0, hot: null, conf: 0 });
  }

  const issues = [];
  const setT = (p, type, dT, cls, hot, conf) => { Object.assign(p, { type, dT: +dT.toFixed(1), cls, hot, conf }); };

  // Offline string: whole table R07-T3 uniformly warm (open-circuit)
  const offPanels = [];
  for (let tr = 0; tr < TROWS; tr++) for (let tc = 0; tc < TCOLS; tc++) {
    const p = panels[idx(OFF.r, OFF.t, tr, tc)]; setT(p, "offline", 4.2 + rnd() * 0.8, 2, null, 0.97); offPanels.push(p.i);
  }
  // PID cluster: panels nearest negative pole of R03-T1 (first 8 of each table-row)
  const pidPanels = [];
  for (let tr = 0; tr < TROWS; tr++) for (let tc = 0; tc < 8; tc++) {
    const p = panels[idx(PIDP.r, PIDP.t, tr, tc)]; setT(p, "pid", 3 + (8 - tc) * 0.9 + rnd(), tc < 3 ? 2 : 1, null, 0.86); pidPanels.push(p.i);
  }
  // Hot cells & bypass diodes
  panels.forEach(p => {
    if (p.type !== "ok") return;
    const u = rnd();
    if (u < 0.011) {
      const dT = 6 + rnd() * 34;
      setT(p, "hot_cell", dT, dT > 20 ? 3 : dT > 10 ? 2 : 1, { c: Math.floor(rnd() * CELL_C), r: Math.floor(rnd() * CELL_R) }, 0.82 + rnd() * 0.16);
    } else if (u < 0.016) {
      setT(p, "bypass", 7 + rnd() * 6, 2, { sub: Math.floor(rnd() * 3) }, 0.9 + rnd() * 0.08);
    } else if (u < 0.0175) {
      setT(p, "cracked", 4 + rnd() * 4, 1, { c: Math.floor(rnd() * CELL_C), r: Math.floor(rnd() * CELL_R) }, 0.71 + rnd() * 0.15);
    }
  });

  // Per-panel cell grid temperatures (°C)
  const cells = (p) => {
    let ss = p.i * 7919 + 17; const r = () => (ss = (ss * 9301 + 49297) % 233280) / 233280;
    const g = [];
    for (let rr = 0; rr < CELL_R; rr++) for (let cc = 0; cc < CELL_C; cc++) {
      let t = T_BASE + (r() - 0.5) * 1.2 + (rr / CELL_R) * 0.8; // slight vertical gradient
      if (p.soil > 0.35) t += (rr > CELL_R - 3 ? p.soil * 2.6 : p.soil * 0.4);
      if (p.bird && rr === 4 && cc === 3) t += 3.2;
      if (p.type === "hot_cell" || p.type === "cracked") {
        const d = Math.hypot(cc - p.hot.c, (rr - p.hot.r) * 0.9);
        t += p.dT * Math.exp(-d * d / (p.type === "cracked" ? 0.9 : 0.5));
      }
      if (p.type === "bypass" && Math.floor(cc / 2) === p.hot.sub) t += p.dT * (0.85 + r() * 0.2);
      if (p.type === "pid") t += p.dT * Math.max(0, 1 - Math.min(cc, CELL_C - 1 - cc, rr, CELL_R - 1 - rr) / 2.2) * 0.9;
      if (p.type === "offline") t += p.dT;
      g.push(+t.toFixed(2));
    }
    return g;
  };
  panels.forEach(p => {
    const g = cells(p);
    p.tmax = Math.max(...g); p.tmean = +(g.reduce((a, b) => a + b, 0) / g.length).toFixed(1);
    if (p.type === "ok") p.dT = +(p.tmax - T_BASE).toFixed(1);
  });

  const lossFrac = (p) => p.type === "offline" ? 1 : p.type === "bypass" ? 0.33 : p.type === "pid" ? (p.cls === 2 ? 0.22 : 0.12)
    : p.type === "hot_cell" ? [0, 0.02, 0.08, 0.15][p.cls] : p.type === "cracked" ? 0.04 : 0;
  const soilFrac = (p) => p.soil > 0.6 ? 0.08 : p.soil > 0.35 ? 0.04 : 0;
  panels.forEach(p => { p.loss = +(MODULE.eur_yr * (lossFrac(p) + soilFrac(p))).toFixed(2); p.thermalLoss = +(MODULE.eur_yr * lossFrac(p)).toFixed(2); });

  // Issue list
  issues.push({ id: "TH-001", type: "offline", panels: offPanels, cls: 2, dT: 4.6, label: "String offline", where: `${rt(OFF)} · 30 modules`,
    string: panels[offPanels[0]].string, loss: +(offPanels.length * MODULE.eur_yr).toFixed(0), conf: 0.97,
    action: "Check string fuse & MC4 connectors at combiner CB-08.3", urgency: "7 days" });
  issues.push({ id: "TH-002", type: "pid", panels: pidPanels, cls: 2, dT: Math.max(...pidPanels.map(i => panels[i].dT)), label: "PID pattern", where: `${rt(PIDP)} · 16 modules`,
    string: panels[pidPanels[0]].string, loss: +pidPanels.reduce((a, i) => a + panels[i].thermalLoss, 0).toFixed(0), conf: 0.86,
    action: "Install anti-PID box on INV-07; IV-curve trace string S01", urgency: "30 days" });
  let n = 3;
  panels.filter(p => ["hot_cell", "bypass", "cracked"].includes(p.type)).sort((a, b) => b.cls - a.cls || b.dT - a.dT).forEach(p => {
    issues.push({ id: `TH-${String(n++).padStart(3, "0")}`, type: p.type, panels: [p.i], cls: p.cls, dT: p.dT,
      label: { hot_cell: "Hot cell", bypass: "Bypass diode active", cracked: "Micro-crack" }[p.type], where: p.id, string: p.string,
      loss: +p.thermalLoss.toFixed(0), conf: p.conf,
      action: p.type === "bypass" ? "Replace junction box / diode" : p.cls === 3 ? "Replace module — fire risk" : p.cls === 2 ? "Schedule module replacement" : "Monitor next cycle",
      urgency: p.cls === 3 ? "48 h" : p.cls === 2 ? "30 days" : "Next cycle" });
  });

  const cleaning = panels.filter(p => p.soil > 0.35).map(p => ({
    i: p.i, id: p.id, string: p.string, soil: p.soil, bird: p.bird,
    level: p.bird && p.soil < 0.6 ? "spot" : p.soil > 0.6 ? "heavy" : "light",
    loss: +(MODULE.eur_yr * soilFrac(p)).toFixed(2),
  }));

  const counts = {
    total: N,
    thermal: panels.filter(p => p.type !== "ok").length,
    cls3: panels.filter(p => p.cls === 3).length,
    cls2: panels.filter(p => p.cls === 2).length,
    cls1: panels.filter(p => p.cls === 1).length,
    cleaning: cleaning.length,
    heavy: cleaning.filter(c => c.level === "heavy").length,
    spot: cleaning.filter(c => c.level === "spot").length,
    healthy: panels.filter(p => p.type === "ok" && p.soil <= 0.35).length,
    lossThermal: Math.round(panels.reduce((a, p) => a + p.thermalLoss, 0)),
    lossSoil: Math.round(cleaning.reduce((a, c) => a + c.loss, 0)),
  };

  const ZONES_UNUSED = ["A", "B", "C", "D"].flatMap((r, ri) => [1, 2, 3, 4].map(c => ({
    id: `${r}${c}`,
  })));

  return { ROWS, TABLES, TCOLS, TROWS, PER_ROW, N, CELL_C, CELL_R, AMBIENT, IRR, T_BASE, MODULE,
    panels, issues, cleaning, counts, cells, loc, zoneId };
}

/* Zones of the farm and their state within a given inspection */
const ZR_ZONE_IDS = ["A", "B", "C", "D"].flatMap(r => [1, 2, 3, 4].map(c => `${r}${c}`));
function zrHash(str) { let h = 7; for (const ch of str) h = (h * 31 + ch.charCodeAt(0)) % 2147483; return h; }
function zrAddDays(d, n) { const t = new Date(d + "T00:00:00Z"); t.setUTCDate(t.getUTCDate() + n); return t.toISOString().slice(0, 10); }
/* Zone IDs: committed zones of the MIC, else the site's zones from setup, else the seed 4×4 grid */
function zrZoneIdsFor(mic) {
  if (mic && mic.zones && mic.zones.length) return mic.zones;
  const site = mic && (window.SITES || []).find(s => s.id === mic.siteId);
  if (site && site.poly && window.siteProfile) return window.siteProfile(site).zones.map(z => z.id);
  return ZR_ZONE_IDS;
}
function zrZonesFor(mic) {
  if (!mic) return [];
  const ids = zrZoneIdsFor(mic), N = ids.length;
  const p = mic.progress || {};
  const flown = Math.round(N * Math.min(1, p.flying || 0));
  const ready = mic.status === "closed" || mic.status === "reported" || mic.status === "review" ? N : Math.round(N * Math.min(1, p.processing || 0));
  const start = mic.startedAt || mic.plannedFor || "2026-04-15";
  return ids.map((id, k) => ({
    id, date: k < flown ? zrAddDays(start, Math.floor(k / 6)) : null,
    status: k < ready ? "ready" : k < Math.min(flown, ready + 2) ? "processing" : k < flown ? "queued" : "notflown",
  }));
}
const ZR_CACHE = {};
function zrGet(micId, zoneId) {
  const k = micId + "/" + zoneId;
  if (!ZR_CACHE[k]) ZR_CACHE[k] = zrBuild(zrHash(k), zoneId);
  return ZR_CACHE[k];
}

/* Iron thermal palette */
function zrIron(t) {
  const st = [[0, [11, 4, 32]], [0.22, [76, 18, 128]], [0.45, [180, 30, 95]], [0.65, [236, 92, 40]], [0.84, [250, 190, 50]], [1, [255, 250, 220]]];
  t = Math.max(0, Math.min(1, t));
  for (let k = 1; k < st.length; k++) if (t <= st[k][0]) {
    const [a, ca] = st[k - 1], [b, cb] = st[k], f = (t - a) / (b - a);
    return `rgb(${ca.map((v, j) => Math.round(v + (cb[j] - v) * f)).join(",")})`;
  }
  return "rgb(255,250,220)";
}

window.ZR = zrGet("MIC-2026-04-033", "C3");
Object.assign(window, { zrIron, zrGet, zrZonesFor, zrZoneIdsFor, ZR_ZONE_IDS });
