/* SkyOps — Production model
   Deterministic monthly production per site with loss attribution, three baselines,
   inverter→string drill-down, deviation alerts and 12-month forecast with/without interventions. */

const PM = (() => {
  const sites = window.SITES || [];
  const SPOT = [78, 71, 64, 52, 47, 44, 49, 55, 61, 69, 75, 82]; // €/MWh Iberian spot, May..Apr
  const MONTHS = ["May 25","Jun 25","Jul 25","Aug 25","Sep 25","Oct 25","Nov 25","Dec 25","Jan 26","Feb 26","Mar 26","Apr 26"];
  const IRR_SEASON = [172, 196, 214, 203, 160, 118, 82, 70, 84, 106, 146, 168]; // kWh/m²/month, Iberia
  const PR_DESIGN = 0.82;
  const hash = (s) => { let h = 7; for (const c of s) h = (h * 31 + c.charCodeAt(0)) % 2147483; return h; };
  const rng = (seed) => { let x = seed % 233280; return () => (x = (x * 9301 + 49297) % 233280) / 233280; };

  const siteModel = (site) => {
    const r = rng(hash(site.id));
    const health = site.health;
    const ppa = 46 + (hash(site.id) % 9);                 // €/MWh PPA
    const ppaShare = 0.7;                                   // 70% PPA, 30% spot
    const months = MONTHS.map((m, i) => {
      const irr = IRR_SEASON[i] * (0.93 + r() * 0.14);
      const expected = site.capacity_mw * 1000 * irr * PR_DESIGN / 1000; // MWh
      const weather = expected * (0.03 + r() * 0.05);     // clouds beyond TMY
      const soilBase = (100 - health) * 0.0009 + 0.012;
      const soilSeason = [1.4,1.6,1.8,1.9,1.5,1.0,0.6,0.4,0.5,0.8,1.1,1.3][i];
      const soiling = expected * soilBase * soilSeason;
      const thermal = expected * ((100 - health) * 0.0011 + 0.004);
      const stringOff = expected * (r() < 0.25 ? 0.006 + r() * 0.012 : 0.002);
      const inverter = expected * (r() < 0.15 ? 0.01 + r() * 0.02 : 0.0015);
      const curtail = expected * (SPOT[i] < 50 ? 0.015 + r() * 0.03 : 0.002);
      const actual = expected - weather - soiling - thermal - stringOff - inverter - curtail;
      const price = ppa * ppaShare + SPOT[i] * (1 - ppaShare);
      return { m, irr, expected, weather, soiling, thermal, stringOff, inverter, curtail, actual, price, spot: SPOT[i] };
    });
    const sum = (k) => months.reduce((a, x) => a + x[k], 0);
    const tot = { expected: sum("expected"), actual: sum("actual"), weather: sum("weather"), soiling: sum("soiling"), thermal: sum("thermal"), stringOff: sum("stringOff"), inverter: sum("inverter"), curtail: sum("curtail") };
    tot.controllable = tot.soiling + tot.thermal + tot.stringOff + tot.inverter;
    tot.eurLost = months.reduce((a, x) => a + (x.soiling + x.thermal + x.stringOff + x.inverter) * x.price, 0);
    tot.eurCurtail = months.reduce((a, x) => a + x.curtail * x.price, 0);
    tot.revenue = months.reduce((a, x) => a + x.actual * x.price, 0);
    tot.pr = PR_DESIGN * tot.actual / tot.expected;
    tot.avail = 1 - (tot.stringOff + tot.inverter) / tot.expected;
    tot.specific = tot.actual / site.capacity_mw; // MWh/MWp
    // Baselines
    tot.base = { physical: tot.expected, contract: tot.expected * 0.93, peer: 0 };
    // Inverters → strings
    const nInv = Math.max(4, Math.round(site.capacity_mw / 18));
    const h = hash(site.id);
    const siteMics = (window.INSPECTIONS || []).filter(i => i.siteId === site.id && i.status !== "scheduled" && i.status !== "draft").sort((a, b) => (b.startedAt || "").localeCompare(a.startedAt || ""));
    const micRef = siteMics[0] ? siteMics[0].id : null;
    const src = (tag) => micRef ? `Inspection ${micRef} (${tag})` : "SCADA only · no recent inspection";
    const offInv = h % nInv, pidInv = (h + 3) % nInv, soilInv = (h + 5) % nInv;
    const offStr = 1 + (h % 12), sinceOff = `${["Mar","Feb","Apr","Jan"][h % 4]} ${String(1 + (h % 27)).padStart(2, "0")}`, sincePid = `${["Feb","Jan","Mar"][h % 3]} ${String(1 + ((h * 7) % 27)).padStart(2, "0")}`, sinceSoil = `Apr ${String(1 + ((h * 3) % 14)).padStart(2, "0")}`;
    const inverters = Array.from({ length: nInv }, (_, k) => {
      const ri = rng(hash(site.id + k));
      const cap = site.capacity_mw / nInv;
      const exp = tot.expected / nInv;
      let loss = ri() * 0.02 + 0.005;
      let cause = "nominal";
      if (k === offInv && health < 90) { loss += 0.11; cause = `offline string S${String(offStr).padStart(2, "0")} since ${sinceOff}`; }
      if (k === pidInv && health < 85 && k !== offInv) { loss += 0.05; cause = `PID cluster · ${12 + (h % 9)} modules`; }
      if (k === soilInv && k !== offInv && k !== pidInv) { loss += 0.028; cause = "soiling · road side"; }
      const strings = Array.from({ length: 12 }, (_, s) => {
        const rs = rng(hash(site.id + k + "s" + s));
        let sl = rs() * 0.03;
        let scause = "";
        if (cause.startsWith("offline") && s === offStr - 1) { sl = 1; scause = "offline"; }
        if (cause.startsWith("PID") && s < 2) { sl = 0.28 + rs() * 0.1; scause = "PID"; }
        if (cause.startsWith("soiling") && s % 3 === 0) { sl = 0.08 + rs() * 0.04; scause = "soiling"; }
        return { id: `S${String(s + 1).padStart(2, "0")}`, loss: sl, cause: scause, zone: ["A","B","C","D"][Math.floor(s / 3)] + ((k % 4) + 1), eur: sl * exp / 12 * (ppa * ppaShare + 58 * 0.3) };
      });
      return { id: `INV-${String(k + 1).padStart(2, "0")}`, cap, expected: exp, actual: exp * (1 - loss), loss, cause, strings, eur: loss * exp * (ppa * ppaShare + 58 * 0.3) };
    });
    // Deviation alerts (last 30 days)
    const alerts = [];
    inverters.forEach(inv => {
      if (inv.cause.startsWith("offline")) alerts.push({ sev: "critical", site, where: inv.id, dev: -(inv.loss * 100), cause: inv.cause, eurMo: inv.eur / 12, since: sinceOff, action: "Dispatch electrician · check string fuses", source: "SCADA + " + src("TH-001") });
      else if (inv.cause.startsWith("PID")) alerts.push({ sev: "high", site, where: inv.id, dev: -(inv.loss * 100), cause: inv.cause, eurMo: inv.eur / 12, since: sincePid, action: "Anti-PID box · IV trace", source: src("TH-002") });
      else if (inv.cause.startsWith("soiling")) alerts.push({ sev: inv.loss > 0.045 ? "high" : "medium", site, where: inv.id, dev: -(inv.loss * 100), cause: inv.cause, eurMo: inv.eur / 12, since: sinceSoil, action: "Schedule robotic wash", source: "Soiling index + satellite" });
    });
    // Interventions ranked by ROI
    const capexWash = site.capacity_mw * 310, capexHot = 24 * 1850 + 6000, capexString = 2400, capexPid = 4800 * Math.ceil(nInv / 6);
    const annualPrice = tot.revenue / tot.actual;
    const interventions = [
      { k: "wash", label: "Robotic wash · full site", cost: capexWash, gain: tot.soiling * 0.75 * annualPrice, when: "≤ 14 days", who: "Cleaning crew", payback: 0 },
      { k: "string", label: "Re-energise offline strings", cost: capexString, gain: tot.stringOff * 0.95 * annualPrice, when: "≤ 7 days", who: "Site electrician" },
      { k: "hot", label: "Replace CoA 3 hot-cell modules", cost: capexHot, gain: tot.thermal * 0.45 * annualPrice, when: "≤ 30 days", who: "O&M + warranty" },
      { k: "pid", label: "Anti-PID boxes on affected inverters", cost: capexPid, gain: tot.thermal * 0.35 * annualPrice, when: "≤ 60 days", who: "Inverter OEM" },
      { k: "wait", label: "Do nothing", cost: 0, gain: 0, when: "—", who: "—" },
    ].map(x => ({ ...x, payback: x.gain > 0 ? x.cost / x.gain * 12 : null, roi: x.cost > 0 ? x.gain / x.cost : 0 })).sort((a, b) => b.gain - b.cost - (a.gain - a.cost));
    // 12-month forecast: baseline degrades, intervened recovers
    const degr = 0.0045; // per month, controllable losses grow
    const forecast = MONTHS.map((m, i) => {
      const exp = months[i].expected;
      const ctrlNow = tot.controllable / tot.expected;
      const noAct = exp * (1 - (months[i].weather + months[i].curtail) / exp - ctrlNow * (1 + degr * (i + 1) * 2.2));
      const withAct = exp * (1 - (months[i].weather + months[i].curtail) / exp - ctrlNow * Math.max(0.25, 1 - 0.14 * (i + 1)));
      return { m: m.replace("25", "26").replace("Jan 26", "Jan 27").replace("Feb 26", "Feb 27").replace("Mar 26", "Mar 27").replace("Apr 26", "Apr 27"), expected: exp, noAct, withAct, price: months[i].price };
    });
    forecast.gap = forecast.reduce((a, f) => a + (f.withAct - f.noAct) * f.price, 0);
    return { site, months, tot, inverters, alerts, interventions, forecast, ppa };
  };

  const models = sites.map(siteModel);
  // peer baseline: median specific yield of same type
  const med = (arr) => { const a = [...arr].sort((x, y) => x - y); return a[Math.floor(a.length / 2)]; };
  models.forEach(mo => { const peers = models.filter(p => p.site.type === mo.site.type); mo.tot.base.peer = med(peers.map(p => p.tot.specific)) * mo.site.capacity_mw; });

  const agg = (list) => {
    const keys = ["expected","actual","weather","soiling","thermal","stringOff","inverter","curtail","controllable","eurLost","eurCurtail","revenue"];
    const t = Object.fromEntries(keys.map(k => [k, list.reduce((a, m) => a + m.tot[k], 0)]));
    t.cap = list.reduce((a, m) => a + m.site.capacity_mw, 0);
    t.pr = PR_DESIGN * t.actual / t.expected;
    t.avail = 1 - (t.stringOff + t.inverter) / t.expected;
    t.specific = t.actual / t.cap;
    t.base = { physical: t.expected, contract: list.reduce((a, m) => a + m.tot.base.contract, 0), peer: list.reduce((a, m) => a + m.tot.base.peer, 0) };
    t.months = MONTHS.map((m, i) => { const o = { m }; ["expected","actual","weather","soiling","thermal","stringOff","inverter","curtail"].forEach(k => o[k] = list.reduce((a, x) => a + x.months[i][k], 0)); o.price = list.length ? list.reduce((a, x) => a + x.months[i].price, 0) / list.length : 0; return o; });
    t.alerts = list.flatMap(m => m.alerts).sort((a, b) => b.eurMo - a.eurMo);
    t.interventions = list.flatMap(m => m.interventions.filter(x => x.k !== "wait").map(x => ({ ...x, site: m.site }))).sort((a, b) => (b.gain - b.cost) - (a.gain - a.cost));
    t.forecast = MONTHS.map((_, i) => ({ m: list[0] ? list[0].forecast[i].m : "", expected: list.reduce((a, x) => a + x.forecast[i].expected, 0), noAct: list.reduce((a, x) => a + x.forecast[i].noAct, 0), withAct: list.reduce((a, x) => a + x.forecast[i].withAct, 0) }));
    t.forecastGap = list.reduce((a, m) => a + m.forecast.gap, 0);
    return t;
  };
  return { MONTHS, SPOT, PR_DESIGN, models, agg, byId: Object.fromEntries(models.map(m => [m.site.id, m])) };
})();

window.PM = PM;
