/* SkyOps — Temporal model
   Deterministic simulation of panel-state evolution across inspection cycles at one site.
   Encodes real failure mechanics: PID contagion, hail bypass band, seasonal soiling + wash,
   hot-cell persistence + work-order repairs, drone-swap calibration confound. */

const TM_COLS = 24, TM_ROWS = 12;
const TM_STATES = ["clean", "soiling", "pid", "hot_cell", "bypass"];
const TM_STATE_META = {
  clean:    { label: "Clean",    color: "#1f3a2e", loss: 0 },
  soiling:  { label: "Soiling",  color: "#a3a33a", loss: 400 },
  pid:      { label: "PID",      color: "#8b5cf6", loss: 1200 },
  hot_cell: { label: "Hot cell", color: "#ef4444", loss: 1800 },
  bypass:   { label: "Bypass",   color: "#f97316", loss: 3000 },
};

const TM_CYCLES = [
  { id: "MIC-2025-07-004", label: "Q3 25", date: "2025-07-12", drone: "M300 · XT2" },
  { id: "MIC-2025-10-008", label: "Q4 25", date: "2025-10-09", drone: "M350 · H30T" },
  { id: "MIC-2026-01-017", label: "Q1 26", date: "2026-01-14", drone: "M350 · H30T" },
  { id: "MIC-2026-04-033", label: "Q2 26", date: "2026-04-15", drone: "M350 · H30T" },
];

/* Events between cycles. `after` = index of the cycle the event follows. */
const TM_EVENTS = [
  { id: "EQ-01",  kind: "equipment", after: 0, date: "2025-09-02", title: "Drone swap M300 → M350",       sub: "Thermal sensor XT2 → H30T · new radiometric calibration" },
  { id: "WX-07",  kind: "weather",   after: 1, date: "2025-11-14", title: "Hailstorm · 22 mm stones",      sub: "IPMA cell passed NE → SW over rows 2–4, 14:20 UTC" },
  { id: "WO-118", kind: "om",        after: 2, date: "2026-02-20", title: "WO-118 · Robotic wash east block", sub: "Cols 18–24 · cost €3,200 · Ecoppia T4" },
  { id: "WO-131", kind: "om",        after: 2, date: "2026-03-02", title: "WO-131 · Replace 6 hot-cell modules", sub: "Cost €11,100 · Longi Hi-MO5 swap" },
  { id: "WX-09",  kind: "weather",   after: 2, date: "2026-03-19", title: "Heavy rain · 41 mm / 24 h",     sub: "Runoff pooling reported near rows 9–10" },
];

function tmRng(seed) { let s = seed; return () => (s = (s * 9301 + 49297) % 233280) / 233280; }

function tmStringId(c, r) { return `S-${String(Math.floor(c / 6) + 1).padStart(2, "0")}-${String(r + 1).padStart(2, "0")}`; }
function tmPanelId(c, r) { return `${String.fromCharCode(65 + Math.floor(r / 3))}-${String(c + 1).padStart(2, "0")}-${String(r + 1).padStart(2, "0")}`; }

const PID_ORIGIN = { c: 4, r: 8 };
const PID_RADIUS = [0.6, 1.5, 2.4, 3.4];

function buildTemporal() {
  const rng = tmRng(1337);
  const N = TM_COLS * TM_ROWS;
  const grid = TM_CYCLES.map(() => new Array(N).fill("clean"));
  const repaired = new Set();
  const lowConfWarm = TM_CYCLES.map(() => 0);
  const hotSeeds = [];

  for (let t = 0; t < TM_CYCLES.length; t++) {
    for (let r = 0; r < TM_ROWS; r++) for (let c = 0; c < TM_COLS; c++) {
      const i = r * TM_COLS + c;
      const u = rng();
      let st = "clean";
      // Soiling — east block seasonal; wash after t=2; drainage regression rows 9-10
      if (c >= 18) {
        const p = [0.62, 0.38, 0.5, (r === 8 || r === 9) ? 0.72 : 0.08][t];
        if (u < p) st = "soiling";
      } else if (u < 0.03) st = "soiling";
      // PID — contagion from origin
      const d = Math.hypot(c - PID_ORIGIN.c, (r - PID_ORIGIN.r) * 1.2);
      if (d <= PID_RADIUS[t]) st = "pid";
      // Hot cells — persistence + new seeds
      const prevHot = t > 0 && grid[t - 1][i] === "hot_cell";
      if (prevHot && !repaired.has(i) && rng() < 0.86) st = "hot_cell";
      else if (rng() < 0.022) st = "hot_cell";
      // Hail bypass band — after event WX-07 (t >= 2)
      if (t >= 2 && r >= 1 && r <= 3 && c >= 13 && c <= 20 && rng() < (t === 2 ? 0.42 : 0.5)) st = "bypass";
      grid[t][i] = st;
    }
    // WO-131 repairs 6 of the hot cells present at t=2 before t=3
    if (t === 2) {
      const hots = grid[2].map((s, i) => s === "hot_cell" ? i : -1).filter(i => i >= 0);
      hots.slice(0, 6).forEach(i => repaired.add(i));
      hotSeeds.push(...hots);
    }
    // Calibration confound: low-confidence warm detections jump after drone swap
    lowConfWarm[t] = [14, 61, 22, 19][t];
  }
  repaired.forEach(i => { if (grid[3][i] === "hot_cell") grid[3][i] = "clean"; });

  const counts = grid.map(g => TM_STATES.reduce((acc, s) => (acc[s] = g.filter(x => x === s).length, acc), {}));
  const lossYr = grid.map(g => g.reduce((a, s) => a + TM_STATE_META[s].loss, 0));

  const transitions = (a, b) => {
    const m = {};
    TM_STATES.forEach(x => { m[x] = {}; TM_STATES.forEach(y => m[x][y] = 0); });
    for (let i = 0; i < N; i++) m[grid[a][i]][grid[b][i]]++;
    return m;
  };

  const persistence = (from, to) => {
    const out = new Array(N).fill(0);
    for (let t = from; t <= to; t++) for (let i = 0; i < N; i++) if (grid[t][i] !== "clean") out[i]++;
    return out;
  };

  // Counterfactual: hot cells at t=2 not repaired and still hot at t=3
  const missed = hotSeeds.filter(i => !repaired.has(i) && grid[3][i] === "hot_cell");
  const missedCost = Math.round(missed.length * TM_STATE_META.hot_cell.loss);

  // PID velocity → forecast
  const pidSeries = counts.map(c => c.pid);
  const pidRate = (pidSeries[3] - pidSeries[1]) / 2; // panels / quarter, recent
  const pidAccel = (pidSeries[3] - pidSeries[2]) - (pidSeries[2] - pidSeries[1]);
  const forecast = [1, 2, 3, 4, 5, 6].map(k => Math.round(pidSeries[3] + pidRate * k + Math.max(0, pidAccel) * k * (k - 1) / 4));
  const PID_THRESHOLD = 60; // string-isolation trip / inverter MPPT window lost
  const qToCliff = forecast.findIndex(v => v >= PID_THRESHOLD) + 1;

  // Action audit
  const eastSoil = (t) => { let n = 0, tot = 0; for (let r = 0; r < TM_ROWS; r++) for (let c = 18; c < TM_COLS; c++) { tot++; if (grid[t][r * TM_COLS + c] === "soiling") n++; } return { n, tot }; };
  const drain = (t) => { let n = 0; for (let c = 18; c < TM_COLS; c++) for (const r of [8, 9]) if (grid[t][r * TM_COLS + c] === "soiling") n++; return n; };
  const s2 = eastSoil(2), s3 = eastSoil(3), d3 = drain(3);
  const cleared = s2.n - (s3.n - d3);
  const audits = [
    { id: "WO-118", title: "Robotic wash east block", cost: 3200,
      before: s2.n, after: s3.n, unit: "soiled panel groups",
      recovered: (s2.n - s3.n) * TM_STATE_META.soiling.loss,
      verdict: "partial", note: `Cleared ${cleared} panel groups, but ${d3} in rows 9–10 re-soiled within 4 weeks.` },
    { id: "WO-131", title: "Replace 6 hot-cell modules", cost: 11100,
      before: counts[2].hot_cell, after: counts[3].hot_cell, unit: "hot cells",
      recovered: 6 * TM_STATE_META.hot_cell.loss,
      verdict: "effective", note: `All 6 replaced modules clean at Q2 26. ${missed.length} untreated hot cells persisted.` },
  ];

  return { grid, counts, lossYr, transitions, persistence, repaired, missed, missedCost,
           pidSeries, pidRate, forecast, PID_THRESHOLD, qToCliff, audits, lowConfWarm, N };
}

window.TM = { TM_COLS, TM_ROWS, TM_STATES, TM_STATE_META, TM_CYCLES, TM_EVENTS, PID_ORIGIN, PID_RADIUS,
  tmStringId, tmPanelId, data: buildTemporal() };
