// Thermograph climate-score subpage — for one location, shows how far the last 6
// years have drifted from the full 45-year record, per metric and season, with an
// equal-weighted overall score and a button-revealed summary. Talks to /api/v2/score.
import { loadLastLocation, saveLastLocation, locHash } from "./nav.js";
import { fmtTemp, onUnitChange } from "./units.js";
import { uv } from "./account.js";   // header sign-in entry + notification bell, and the API-version resolver
import { loadView, TTL, prefetchViews } from "./cache.js";
import { initFindButton, setFindLabel } from "./mappicker.js";
import { TIER_COLORS, esc, placeLabel } from "./shared.js";

// Display order (site convention leads with Precip).
const METRIC_ORDER = ["precip", "tmax", "tmin", "feels", "humid", "wetbulb", "wind", "gust"];
const SEASON_NAMES = { djf: "Winter", mam: "Spring", jja: "Summer", son: "Fall" };
const SEASON_ORDER = ["djf", "mam", "jja", "son"];

const tintColor = (cls) => TIER_COLORS[cls] || "";
const tintClass = (cls) => `t-${cls}`;

// A signed-points chip, colored up (▲) or down (▼): "+3" / "-2".
const signedPts = (d, suffix = "") =>
  `<span class="pq-d ${d >= 0 ? "pq-up" : "pq-down"}">${d >= 0 ? "+" : ""}${d}${suffix}</span>`;

// One tinted score cell (an em-dash when the entry has no score). `strong` bolds
// the number for an Overall row/column; the tint + hover title come from the entry.
function scoreCell(entry, strong = false) {
  if (!entry || entry.score == null) return `<td class="t-none">—</td>`;
  const title = entry.label ? `${entry.label}: ${entry.grade}` : entry.grade;
  const num = strong ? `<strong>${entry.score}</strong>` : `${entry.score}`;
  return `<td class="${tintClass(entry.class)}" title="${esc(title)}">${num}</td>`;
}

let selected = null;   // {lat, lon}
let lastData = null;   // most recent /score response, for repainting on a unit switch

const placeholder = document.getElementById("score-placeholder");
const scoreHead = document.getElementById("score-head");
const scoreBody = document.getElementById("score-body");

// ---- location picker ----
const findBtn = document.getElementById("find-btn");
const locLabel = document.getElementById("loc-label");
initFindButton(findBtn, "Find a location", () => selected,
  (lat, lon) => {
    selected = { lat, lon };
    locLabel.textContent = `${lat.toFixed(3)}, ${lon.toFixed(3)}`;
    locLabel.hidden = false;
    fetchScore();
  });

// ---- fetch + render ----
let seqCounter = 0;   // supersedes an in-flight load when the user picks a new spot

async function fetchScore() {
  if (!selected) return;
  history.replaceState(null, "", locHash(selected.lat, selected.lon));
  placeholder.hidden = true;
  scoreHead.hidden = false;
  await loadView({
    url: uv(`score?lat=${selected.lat}&lon=${selected.lon}`),
    ttl: TTL.score, seq: ++seqCounter, current: () => seqCounter,
    spinner: () => {
      scoreHead.innerHTML = `<p class="spinner">Scoring 45 years of climate…</p>`;
      scoreBody.innerHTML = "";
    },
    onData: finishScore,
    onError: (err) => {
      scoreHead.innerHTML = `<p class="error">${esc(err.message)}</p>`;
      scoreBody.innerHTML = "";
    },
  });
}

function finishScore(data) {
  saveLastLocation(selected.lat, selected.lon);
  render(data);
  prefetchViews(selected.lat, selected.lon, []);  // warm the other views
}

onUnitChange(() => { if (lastData) render(lastData); });

function render(data) {
  lastData = data;
  const place = placeLabel(data);
  locLabel.textContent = place;
  locLabel.hidden = false;
  setFindLabel(findBtn, "Change location");

  const s = data.scores;
  if (s.unavailable) {
    scoreHead.innerHTML = `<div class="score-hero"><h2>${esc(place)}</h2>
      <p class="score-sub">${esc(s.unavailable)}</p></div>`;
    scoreBody.innerHTML = "";
    return;
  }

  const annual = s.slices.annual;
  const ov = annual.overall;
  const rr = s.recent_range, br = s.baseline_range;
  const recentYrs = `${rr[0].slice(0, 4)}–${rr[1].slice(0, 4)}`;
  const baseYrs = `${br[0].slice(0, 4)}–${br[1].slice(0, 4)}`;

  // ---- hero: the overall annual score ----
  scoreHead.innerHTML = `
    <div class="score-hero" style="--cat:${tintColor(ov.class)}">
      <p class="section-title score-eyebrow">Climate-shift score · ${esc(place)}</p>
      <div class="score-hero-row">
        <span class="score-num">${ov.score}</span>
        <div class="score-hero-label">
          <span class="score-grade">${esc(ov.grade)}</span>
          <span class="score-sub">Net change · ${recentYrs} vs ${baseYrs} · ${s.n_recent.toLocaleString()} recent days scored</span>
        </div>
      </div>
      <p class="score-note">A net-change total across all metrics: 0 = no change from the long-term
        normal · 100 = an extreme shift. Every metric counts equally.</p>
    </div>`;

  // ---- per-metric cards (annual) ----
  const cards = METRIC_ORDER.map((k) => metricCard(annual.metrics[k])).join("");
  const wbNote = wetbulbNote(annual.metrics.wetbulb, baseYrs);

  // ---- by-season table (seasons × metrics, columns aligned) ----
  const seasonHead = `<tr><th></th><th>Overall</th>${METRIC_ORDER.map((k) =>
    `<th>${shortLabel(k)}</th>`).join("")}</tr>`;
  const seasonRows = SEASON_ORDER.map((sk) => {
    const sl = s.slices[sk];
    const cells = METRIC_ORDER.map((k) => scoreCell(sl.metrics[k])).join("");
    return `<tr><th>${SEASON_NAMES[sk]}</th>${scoreCell(sl.overall, true)}${cells}</tr>`;
  }).join("");

  scoreBody.innerHTML = `
    <section class="normals">${cards}</section>
    ${wbNote}
    <section class="score-section">
      <p class="section-title">By season</p>
      <div class="table-wrap"><table class="score-table season-table">
        <thead>${seasonHead}</thead><tbody>${seasonRows}</tbody></table></div>
    </section>
    <button type="button" class="summary-toggle" aria-expanded="false" aria-controls="score-summary">
      Summary <span class="summary-caret" aria-hidden="true">▾</span></button>
    <section id="score-summary" hidden>${summaryHTML(s)}</section>`;

  wireSummaryToggle();
}

// Explains what wet bulb is and reports the share of heat-stress ("wet-bulb")
// days vs normal days, recent vs baseline. Re-rendered on unit change.
function wetbulbNote(m, baseYrs) {
  const def = `<strong>Wet bulb</strong> is the lowest temperature that evaporating sweat can cool you to.
    When it climbs, sweat can't shed heat fast enough and hot days turn dangerous.`;
  if (!m || m.score == null || !m.freq) {
    return `<p class="score-callout">${def}</p>`;
  }
  const thr = fmtTemp(m.freq.threshold_f);
  const { f6, f45, d } = m.freq;
  const delta = d ? ` (${signedPts(d, " pts")})` : "";
  return `<p class="score-callout">${def}
    Heat-stress days (peak wet bulb ≥ ${esc(String(thr))}): <strong>${f6}%</strong> of recent days
    vs <strong>${f45}%</strong> across ${baseYrs}${delta}. The rest are normal.</p>`;
}

// One metric score card. The tier and its direction stack under the score on
// their own lines (rather than crammed top-right), so a long grade like
// "Extreme shift — windier" never clusters in the corner on a narrow card.
function metricCard(m) {
  if (m.score == null) {
    return `<section class="normal-card score-card">
      <h3>${esc(m.label)}</h3>
      <div class="big" style="--cat:var(--muted)">—</div>
      <div class="range">${esc(m.reason || "no data")}</div>
    </section>`;
  }
  return `<section class="normal-card score-card" style="--cat:${tintColor(m.class)}">
    <h3>${esc(m.label)}</h3>
    <div class="big">${m.score}</div>
    <div class="nc-tier">${esc(m.tier)}</div>
    <div class="range">${scoreDetail(m)}</div>
  </section>`;
}

// The muted detail line under a card's tier — the signed drift, or a steady note.
function scoreDetail(m) {
  if (m.score < 15) return "close to the long-term normal";
  return `~${Math.abs(m.bias).toFixed(0)} pts ${esc(m.direction)}`;
}

const shortLabel = (k) => ({ precip: "Precip", tmax: "High", tmin: "Low", feels: "Feels",
  humid: "Humid", wetbulb: "Wet bulb", wind: "Wind", gust: "Gust" })[k] || k;

// ---- summary (revealed by the button) ----
function summaryHTML(s) {
  return sentencesHTML(s.slices.annual) + scoreTableHTML(s) + perQTableHTML(s.slices.annual);
}

// A plain-language sentence per metric that actually shifted.
function sentencesHTML(annual) {
  const lines = METRIC_ORDER
    .map((k) => annual.metrics[k])
    .filter((m) => m && m.score != null && m.score >= 15)
    .map((m) => {
      const mid = m.per_q && m.per_q.find((q) => q.q === 50);
      const where = mid ? ` At the median, recent readings sit about ${Math.abs(mid.d)} points ${mid.d >= 0 ? "higher" : "lower"} than the seasonal norm.` : "";
      return `<li><strong>${esc(m.label)}:</strong> ${esc(m.grade)}, about ${Math.abs(m.bias).toFixed(0)} percentile points ${esc(m.direction)} on average.${where}</li>`;
    });
  if (!lines.length) return `<p class="score-summary-lead">Every metric is close to its long-term normal here, with no notable recent drift.</p>`;
  return `<p class="score-summary-lead">What has shifted the most:</p><ul class="score-sentences">${lines.join("")}</ul>`;
}

// Metrics × slices table of scores, each cell tinted by its tier.
function scoreTableHTML(s) {
  const slices = ["annual", ...SEASON_ORDER];
  const head = `<tr><th>Metric</th>${slices.map((sk) =>
    `<th>${sk === "annual" ? "Annual" : SEASON_NAMES[sk]}</th>`).join("")}</tr>`;
  const rows = METRIC_ORDER.map((k) => {
    const cells = slices.map((sk) => scoreCell(s.slices[sk].metrics[k])).join("");
    const label = s.slices.annual.metrics[k]?.label || k;
    return `<tr><th>${esc(label)}</th>${cells}</tr>`;
  }).join("");
  const overallCells = slices.map((sk) => scoreCell(s.slices[sk].overall, true)).join("");
  const overallRow = `<tr class="score-table-overall"><th>Overall</th>${overallCells}</tr>`;
  return `<p class="section-title">All scores</p>
    <div class="table-wrap"><table class="score-table">
      <thead>${head}</thead><tbody>${rows}${overallRow}</tbody></table></div>`;
}

// The per-percentile detail: for each band, the mean shift (in percentile points)
// averaged across the four seasons — the differentials the score is built from.
function perQTableHTML(annual) {
  const rows = METRIC_ORDER
    .map((k) => annual.metrics[k])
    .filter((m) => m && m.score != null && m.per_q && m.per_q.length)
    .map((m) => {
      const cells = m.per_q.map((q) => `<td>p${q.q}: ${signedPts(q.d)}</td>`).join("");
      return `<tr><th>${esc(m.label)}</th>${cells}</tr>`;
    }).join("");
  if (!rows) return "";
  return `<p class="section-title">Shift by percentile band</p>
    <p class="score-note">The mean shift at each percentile band, averaged across all four seasons (percentile points; ▲ higher / ▼ lower than the seasonal norm).</p>
    <div class="table-wrap"><table class="score-table pq-table"><tbody>${rows}</tbody></table></div>`;
}

// Mirror of shared.js initSeasonExpand: flip [hidden] + aria-expanded.
function wireSummaryToggle() {
  const btn = scoreBody.querySelector(".summary-toggle");
  const panel = scoreBody.querySelector("#score-summary");
  if (!btn || !panel) return;
  btn.addEventListener("click", () => {
    const show = panel.hidden;
    panel.hidden = !show;
    btn.setAttribute("aria-expanded", String(show));
  });
}

// ---- restore (hash from a shared link, else the last place you looked at) ----
(function restore() {
  const loc = loadLastLocation();
  if (loc) {
    selected = { lat: loc.lat, lon: loc.lon };
    fetchScore();
  }
})();
