// Thermograph single-day detail subpage — for one day + location, shows the value
// at every percentile tier boundary in that day-of-year's ±7-day climate window,
// and where the observed values land. Talks to /api/v2/day + /api/v2/geocode.
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 { getJSON, TTL, prefetchViews } from "./cache.js";
import { initFindButton, setFindLabel } from "./mappicker.js";
import { TIER_COLORS, PRECIP_COLORS, DRY_COLOR, pctOrd, fmtPrecip, fmtPrecipTier, fmtWind, fmtHumid,
         todayISO, isoOfDate, weatherType, placeLabel } from "./shared.js";

// Color a tier the same way the calendar cell would be colored for it.
const tierColor = (c) => (c === "dry" ? DRY_COLOR : TIER_COLORS[c] || PRECIP_COLORS[c] || "");

let selected = null;   // {lat, lon}
let curDate = null;    // "YYYY-MM-DD" currently shown
let latest = null;     // newest date available in the record

const placeholder = document.getElementById("day-placeholder");
const dayHead = document.getElementById("day-head");
const dayBody = document.getElementById("day-body");
const dateInput = document.getElementById("date-input");

// ---- location picker ----
// The Find button opens the shared modal map picker; choosing a spot loads it.
const findBtn = document.getElementById("find-btn");
const locLabel = document.getElementById("loc-label");
initFindButton(findBtn, "Find a location", () => selected,
  (lat, lon) => {
    selected = { lat, lon };
    // Show the raw coordinates immediately; render() replaces them with the
    // resolved neighborhood + city name once the day fetch returns.
    locLabel.textContent = `${lat.toFixed(3)}, ${lon.toFixed(3)}`;
    locLabel.hidden = false;
    fetchDay();
  });

// ---- date navigation ----
dateInput.addEventListener("change", () => { if (dateInput.value) { curDate = dateInput.value; fetchDay(); } });
document.getElementById("prev-day").addEventListener("click", () => stepDay(-1));
document.getElementById("next-day").addEventListener("click", () => stepDay(1));

function stepDay(delta) {
  if (!curDate) return;
  const d = new Date(curDate + "T00:00:00");
  d.setDate(d.getDate() + delta);
  // Format in LOCAL time — curDate was parsed as local midnight, so toISOString()
  // (UTC) would shift the result a day for UTC± viewers and desync the today guard.
  const iso = isoOfDate(d);
  if (iso > todayISO()) return;   // don't step past today
  curDate = iso;
  fetchDay();
}

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

async function fetchDay() {
  if (!selected) return;
  const dateQ = curDate ? `&date=${curDate}` : "";
  history.replaceState(null, "", locHash(selected.lat, selected.lon, curDate));
  placeholder.hidden = true;
  dayHead.hidden = false;
  const seq = ++daySeq;
  // Delay the spinner: a cache-served render lands in a few ms, so blanking the
  // page immediately would just flash. Only a slow (network) load shows it.
  const spin = setTimeout(() => {
    if (seq !== daySeq) return;
    dayHead.innerHTML = `<p class="spinner">Building the percentile breakdown…</p>`;
    dayBody.innerHTML = "";
  }, 150);
  let data;
  try {
    // Stale-while-revalidate: a stale cached copy renders immediately; the update
    // callback repaints if the background revalidation finds new data.
    data = await getJSON(
      uv(`day?lat=${selected.lat}&lon=${selected.lon}${dateQ}`), TTL.day,
      false, (upd) => { if (seq === daySeq) finishDay(upd); });
  } catch (err) {
    clearTimeout(spin);
    if (seq !== daySeq) return;
    dayHead.innerHTML = `<p class="error">${err.message}</p>`;
    dayBody.innerHTML = "";
    return;
  }
  clearTimeout(spin);
  if (seq !== daySeq) return;
  finishDay(data);
}

function finishDay(data) {
  latest = data.latest;
  curDate = data.detail.date;
  dateInput.value = curDate;
  // Allow navigating up to today (recent days beyond the archive come from the
  // forecast bundle), not just the latest fully-archived day.
  dateInput.max = todayISO();
  document.getElementById("next-day").disabled = curDate >= todayISO();
  saveLastLocation(selected.lat, selected.lon, curDate);
  render(data);
  prefetchViews(selected.lat, selected.lon, ["day"]);  // warm weekly/calendar/forecast
}

let lastData = null;   // most recent /day response, for repainting on a unit switch
onUnitChange(() => { if (lastData) render(lastData); });

function render(data) {
  lastData = data;
  const d = data.detail;
  const place = placeLabel(data);
  locLabel.textContent = place;
  locLabel.hidden = false;
  setFindLabel(findBtn, "Change location");
  const dt = new Date(d.date + "T00:00:00");
  const nice = dt.toLocaleDateString(undefined, { weekday: "long", year: "numeric", month: "long", day: "numeric" });
  const yrs = d.year_range ? `${d.year_range[0]}–${d.year_range[1]}` : "—";
  // Weather summary from the observed high + precip (omitted for days with no obs yet).
  const th = d.metrics.tmax.obs ? d.metrics.tmax.obs.value : null;
  const pr = d.metrics.precip.obs ? d.metrics.precip.obs.value : null;
  const prCls = d.metrics.precip.obs ? d.metrics.precip.obs.class : null;
  const wt = th != null || pr != null ? weatherType(th, pr, undefined, prCls) : null;
  dayHead.innerHTML = `
    <h2>${nice}</h2>
    ${wt ? `<p class="day-weather">${wt.icon} ${wt.text}</p>` : ""}
    <p class="meta">${place}
       · ${d.n_samples.toLocaleString()} days around this date (${yrs})</p>`;

  dayBody.innerHTML = [
    ladderCard("Daily high", d.metrics.tmax, fmtTemp, "temp"),
    ladderCard("Daily low", d.metrics.tmin, fmtTemp, "temp"),
    ladderCard("Feels like", d.metrics.feels, fmtTemp, "temp"),
    ladderCard("Humidity", d.metrics.humid, fmtHumid, "temp"),
    ladderCard("Wind speed", d.metrics.wind, fmtWind, "temp"),
    ladderCard("Wind gust", d.metrics.gust, fmtWind, "temp"),
    ladderCard("Precipitation", d.metrics.precip, fmtPrecip, "precip"),
  ].join("");
}

// Collapse a space-separated unit shared by both bounds ("17.3 g/m³–19.9 g/m³"
// → "17.3–19.9 g/m³") so ranges fit the narrow value column on phones. Attached
// units (76°, 0.25") pass through untouched.
function fmtRange(fmt, lo, hi) {
  const a = fmt(lo), b = fmt(hi);
  const i = a.lastIndexOf(" ");
  if (i > 0 && b.endsWith(a.slice(i))) return `${a.slice(0, i)}–${b}`;
  return `${a}–${b}`;
}
// The unit-less value for the ◀ marker — the unit is already on the range beside
// it and in the card's summary, and dropping it keeps the marker on one line.
const bareVal = (fmt, v) => {
  const s = fmt(v), i = s.lastIndexOf(" ");
  return i > 0 ? s.slice(0, i) : s;
};

// One metric card: an observed summary line + the tier ladder (highest tier on
// top). The tier the observed value falls into is highlighted with its value.
function ladderCard(title, m, fmt, kind) {
  if (!m || !m.ladder) return "";
  const obs = m.obs;
  const activeClass = obs ? obs.class : null;
  // The observed precip depth reads "trace" for a sub-0.01" rain day (rounds to 0
  // but is graded a rain tier); ladder tier ranges keep the plain numeric formatter.
  const obsFmt = kind === "precip" ? ((v) => fmtPrecipTier(v, activeClass)) : fmt;

  let summary;
  if (obs) {
    const pct = obs.percentile == null ? "" : ` · ${pctOrd(obs.percentile)} pct`;
    summary = `<span class="day-obs" style="--cat:${tierColor(obs.class)}">${obsFmt(obs.value)}</span>
      <span class="day-obs-meta">${obs.grade}${pct}</span>`;
  } else {
    summary = `<span class="day-obs-meta">No observation for this day yet</span>`;
  }

  // Left: a per-metric summary. Right: the bold, right-aligned Historical Range.
  const noteLeft = kind === "temp"
    ? `median ${fmt(m.ladder.median)}`
    : `${m.ladder.rain_days.toLocaleString()} rain days · dry ${m.ladder.dry_pct}%`;
  const note = `<span class="ladder-note-left">${noteLeft}</span>` +
    `<span class="ladder-range">Historical Range ${fmtRange(fmt, m.ladder.min, m.ladder.max)}</span>`;

  const rows = m.ladder.tiers.map((t) => {
    const active = t.c === activeClass ? " active" : "";
    // Dry has no rain percentile — leave that column blank and show the threshold
    // as the value. Other tiers show their percentile range and value range.
    let val, pct = t.range;
    if (t.c === "dry") { val = t.range; pct = ""; }
    else if (t.hi == null) val = `> ${fmt(t.lo)}`;   // top tier: strictly beyond p99
    else if (t.lo == null) val = `< ${fmt(t.hi)}`;   // bottom tier: strictly below p1
    else val = fmtRange(fmt, t.lo, t.hi);
    const marker = t.c === activeClass && obs
      ? `<span class="ladder-mark">◀ ${bareVal(obsFmt, obs.value)}</span>` : "";
    return `<div class="ladder-row${active}">
      <span class="ladder-sw" style="background:${tierColor(t.c)}"></span>
      <span class="ladder-label" style="--cat:${tierColor(t.c)}">${t.label}</span>
      <span class="ladder-pct">${pct}</span>
      <span class="ladder-val">${val}</span>
      <span class="ladder-mk">${marker}</span>
    </div>`;
  }).join("");

  return `<section class="ladder-card">
    <div class="ladder-head">
      <h3>${title}</h3>
      <div class="ladder-summary">${summary}</div>
    </div>
    <p class="ladder-note">${note}</p>
    <div class="ladder">${rows}</div>
  </section>`;
}

// ---- 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 };
    curDate = loc.date || todayISO();   // default to today when no date is given
    fetchDay();
  }
})();
