/**
 * Registered scorer (protocol-regions.json): life expectancy at birth in European NUTS 2 regions, ten
 * years ahead, from 3-year means. Fitted only on countries (WPP 2024), 30 years up to the origin;
 * each region keeps its own recent departure. `--check` prints only how many regions qualify.
 *
 *   node experiment/lives/score-regions.ts WPP.csv.gz demo_r_mlifexp.tsv.gz [--check]
 */
import { readFileSync } from "node:fs";
import { gunzipSync } from "node:zlib";
import { createHash } from "node:crypto";
import { robustFor, bestGapFor } from "./regional.ts";
import { loadE0 } from "./wpp.ts";
import type { Series } from "./e0.ts";

const P = JSON.parse(readFileSync(new URL("./protocol-regions.json", import.meta.url), "utf8"));
const [wppPath, euPath] = [process.argv[2]!, process.argv[3]!], check = process.argv.includes("--check");
const { female, male } = loadE0(wppPath);

// Eurostat: freq,unit,sex,age,geo then one column per year; a value may carry a flag ("79.9 e"), ":" is missing
const raw = gunzipSync(readFileSync(euPath)).toString("utf8").split(/\r?\n/);
const years = raw[0]!.split("\t").slice(1).map((y) => Number(y.trim()));
const regions: Record<string, Series[]> = { F: [], M: [] };
for (const line of raw.slice(1)) {
  if (!line) continue; const f = line.split("\t"), k = f[0]!.split(",");
  const [, unit, sex, age, geo] = k;
  if (unit !== "YR" || age !== "Y_LT1" || (sex !== "F" && sex !== "M") || geo!.length !== 4 || geo!.endsWith("ZZ")) continue;
  const e = new Map<number, number>();
  f.slice(1).forEach((v, i) => { const x = parseFloat(v.trim()); if (Number.isFinite(x)) e.set(years[i]!, x); });
  regions[sex]!.push({ id: geo!, e0: e });
}
const C = P.origin_centre as number, H = P.horizon as number, T = C + H, FROM = C - P.fit_years;
const mean3 = (e: Map<number, number>, y: number) => (e.has(y - 1) && e.has(y) && e.has(y + 1) ? (e.get(y - 1)! + e.get(y)! + e.get(y + 1)!) / 3 : undefined);
const out: Record<string, unknown> = {
  protocolSha256: createHash("sha256").update(readFileSync(new URL("./protocol-regions.json", import.meta.url))).digest("hex"),
  dataSha256: createHash("sha256").update(readFileSync(euPath)).digest("hex"),
};
let met = true;
for (const [sex, nat] of [["F", female], ["M", male]] as const) {
  const fitOn = nat.map((s) => ({ id: s.id, e0: new Map([...s.e0].filter(([y]) => y >= FROM && y <= C)) }));
  const chosen: Series[] = [], truth = new Map<string, number>();
  for (const r of regions[sex]!) {
    const sm = new Map<number, number>();
    for (let y = C - 15; y <= C; y++) { const m = mean3(r.e0, y); if (m !== undefined) sm.set(y, m); }
    const o = sm.get(C); if (o === undefined || o < P.level_at_origin) continue;
    let pairs = 0; for (let y = C - 15; y < C; y++) if (sm.has(y) && sm.has(y + 1)) pairs++;
    if (pairs < P.min_history_pairs) continue;
    if (!r.e0.has(T - 1) || !r.e0.has(T) || !r.e0.has(T + 1)) continue; // availability only; values unread in --check
    chosen.push({ id: r.id, e0: sm });
    if (!check) truth.set(r.id, mean3(r.e0, T)!);
  }
  if (check) { out[sex] = { regions: chosen.length }; continue; }
  const rf = robustFor(fitOn, chosen, C, H), bf = bestGapFor(fitOn, chosen, C, H);
  const err: number[] = [], base: number[] = [], rows: Array<[string, number, number]> = [];
  for (const r of chosen) {
    const f = (rf.get(r.id)![T]! + bf.get(r.id)![T]!) / 2, t = truth.get(r.id)!;
    // baseline: the region's own straight-line trend over the 10 smoothed years up to the origin
    const xs: number[] = [], ys: number[] = []; for (let y = C - 10; y <= C; y++) if (r.e0.has(y)) { xs.push(y); ys.push(r.e0.get(y)!); }
    const mx = xs.reduce((a, b) => a + b, 0) / xs.length, my = ys.reduce((a, b) => a + b, 0) / ys.length;
    const b = xs.reduce((s, x, i) => s + (x - mx) * (ys[i]! - my), 0) / xs.reduce((s, x) => s + (x - mx) ** 2, 0);
    err.push(Math.abs(f - t)); base.push(Math.abs(my + b * (T - mx) - t)); rows.push([r.id, +f.toFixed(2), t]);
  }
  const mae = (a: number[]) => +(a.reduce((x, y) => x + y, 0) / a.length).toFixed(3);
  const sorted = [...err].sort((a, b) => a - b);
  const m = mae(err); met &&= m <= P.bar_years;
  out[sex] = { regions: chosen.length, mae: m, baselineMae: mae(base), medianError: +sorted[sorted.length >> 1]!.toFixed(3), within1y: +(err.filter((e) => e <= 1).length / err.length).toFixed(3), meanSigned: +(rows.reduce((s, [, f, t]) => s + f - t, 0) / rows.length).toFixed(3), regionsDetail: rows };
}
if (!check) out.primary_met = met;
console.log(JSON.stringify(out, null, 1));
