/**
 * =============================================================================
 * QWHPI — Quebec Weekly Housing Price Index
 * Author  : Simon-Pierre Boucher
 * Contact : contact@spboucher.ai
 * File    : web/app/map/page.tsx
 * Purpose : Map — choropleth of the 17 administrative regions (YoY, 13w,
 *           index level, assessment gap) rendered as inline SVG.
 * =============================================================================
 */
"use client";

import { useEffect, useMemo, useRef, useState } from "react";
import { fetchMap, fetchSeries, type MapCell } from "../../lib/api";

const METRICS = [
  { id: "yoy", label: "YoY %" },
  { id: "six_month", label: "6-month %" },
  { id: "three_month", label: "3-month %" },
  { id: "index_level", label: "Index level" },
  { id: "assessment_gap", label: "Assessment gap" },
];
const TYPES = ["all", "unifamilial", "condo", "plex"];

// Sequential blue ramp (light→dark) from the validated palette.
const RAMP = ["#cde2fb", "#9ec5f4", "#6da7ec", "#3987e5", "#256abf", "#184f95", "#0d366b"];

interface Feature {
  properties: { geography_id: string; name: string };
  geometry: { type: string; coordinates: number[][][] | number[][][][] };
}

function project([lng, lat]: number[], w: number, h: number): [number, number] {
  // Quebec bbox in EPSG:4326; simple equirectangular with cos(lat0) x-scale.
  const [minLng, maxLng, minLat, maxLat] = [-79.9, -56.9, 44.9, 62.8];
  const k = Math.cos((52 * Math.PI) / 180);
  const spanX = (maxLng - minLng) * k;
  const spanY = maxLat - minLat;
  const s = Math.min(w / spanX, h / spanY);
  const x = ((lng - minLng) * k) * s;
  const y = h - (lat - minLat) * s - (h - spanY * s) / 2;
  return [x, y];
}

function ringsOf(f: Feature): number[][][] {
  return f.geometry.type === "Polygon"
    ? (f.geometry.coordinates as number[][][])
    : (f.geometry.coordinates as number[][][][]).flat();
}

function pathFor(f: Feature, w: number, h: number): string {
  return ringsOf(f)
    .map((ring) => {
      const pts = ring.map((c) => project(c, w, h));
      return "M" + pts.map(([x, y]) => `${x.toFixed(1)},${y.toFixed(1)}`).join("L") + "Z";
    })
    .join(" ");
}

export default function MapPage() {
  const [metric, setMetric] = useState("yoy");
  const [ptype, setPtype] = useState("all");
  const [cells, setCells] = useState<MapCell[]>([]);
  const [periods, setPeriods] = useState<string[]>([]);
  const [idx, setIdx] = useState(0);
  const [playing, setPlaying] = useState(false);
  const [features, setFeatures] = useState<Feature[]>([]);
  const [hover, setHover] = useState<MapCell | null>(null);
  const [error, setError] = useState<string | null>(null);
  const playRef = useRef<ReturnType<typeof setInterval> | null>(null);

  useEffect(() => {
    fetch("/regions.geojson")
      .then((r) => r.json())
      .then((g) => setFeatures(g.features))
      .catch((e) => setError(String(e)));
    fetchSeries("quebec", "all")
      .then((s) => {
        const ps = s.observations
          .filter((o) => !o.is_partial_month)
          .map((o) => o.period);
        setPeriods(ps);
        setIdx(ps.length - 1);
      })
      .catch((e) => setError(String(e)));
  }, []);

  const period = periods[idx] ?? "";

  useEffect(() => {
    if (!period) return;
    fetchMap(metric, ptype, period)
      .then((m) => {
        setCells(m.cells);
        setError(null);
      })
      .catch((e) => setError(String(e)));
  }, [metric, ptype, period]);

  // Time-lapse playback across all months.
  useEffect(() => {
    if (!playing) {
      if (playRef.current) clearInterval(playRef.current);
      return;
    }
    playRef.current = setInterval(() => {
      setIdx((i) => {
        if (i >= periods.length - 1) {
          setPlaying(false);
          return i;
        }
        return i + 1;
      });
    }, 550);
    return () => {
      if (playRef.current) clearInterval(playRef.current);
    };
  }, [playing, periods.length]);

  const { colorOf, domain } = useMemo(() => {
    const vals = cells.map((c) => c.value).filter((v): v is number => v != null);
    const lo = Math.min(...vals);
    const hi = Math.max(...vals);
    const f = (v: number | null) => {
      if (v == null || vals.length === 0) return "var(--surface-2)";
      const t = hi === lo ? 0.5 : (v - lo) / (hi - lo);
      return RAMP[Math.min(RAMP.length - 1, Math.floor(t * RAMP.length))];
    };
    return { colorOf: f, domain: vals.length ? ([lo, hi] as const) : null };
  }, [cells]);

  const byId = useMemo(
    () => new Map(cells.map((c) => [c.geography_id, c])),
    [cells],
  );

  const W = 720, H = 560;
  const fmt = (v: number | null) =>
    v == null ? "—" : metric === "assessment_gap" ? v.toFixed(2) : v.toFixed(1);

  return (
    <>
      <h1>Regional map</h1>
      <div className="controls">
        <label>Metric</label>
        <select value={metric} onChange={(e) => setMetric(e.target.value)}>
          {METRICS.map((m) => <option key={m.id} value={m.id}>{m.label}</option>)}
        </select>
        <label>Type</label>
        <select value={ptype} onChange={(e) => setPtype(e.target.value)}>
          {TYPES.map((t) => <option key={t} value={t}>{t}</option>)}
        </select>
        <button className="ctrl" aria-pressed={playing}
                aria-label={playing ? "Pause time-lapse" : "Play time-lapse"}
                onClick={() => {
                  if (!playing && idx >= periods.length - 1) setIdx(12);
                  setPlaying((v) => !v);
                }}>
          {playing ? "⏸ Pause" : "▶ Play"}
        </button>
        <input
          type="range"
          min={0}
          max={Math.max(periods.length - 1, 0)}
          value={idx}
          aria-label="Month selector"
          style={{ flex: "1 1 180px", accentColor: "var(--series-1)" }}
          onChange={(e) => {
            setPlaying(false);
            setIdx(Number(e.target.value));
          }}
        />
        <span className="note" style={{ fontVariantNumeric: "tabular-nums",
                                        minWidth: 84 }}>
          <strong>{period}</strong>
        </span>
      </div>
      {error && <div className="card"><span className="note">{error}</span></div>}

      <div className="grid cols-2">
        <div className="card">
          <svg viewBox={`0 0 ${W} ${H}`} role="img"
               aria-label={`Choropleth of Quebec regions, ${metric}`}>
            {features.map((f) => {
              const cell = byId.get(f.properties.geography_id);
              return (
                <path
                  key={f.properties.geography_id}
                  d={pathFor(f, W, H)}
                  fill={colorOf(cell?.value ?? null)}
                  stroke="var(--surface-1)"
                  strokeWidth={1.5}
                  style={{ cursor: "pointer", transition: "fill 0.4s ease" }}
                  onMouseEnter={() => setHover(cell ?? null)}
                  onMouseLeave={() => setHover(null)}
                />
              );
            })}
          </svg>
          {domain && (
            <div className="note" style={{ display: "flex", gap: 4, alignItems: "center" }}>
              {fmt(domain[0])}
              {RAMP.map((c) => (
                <span key={c} style={{ background: c, width: 22, height: 10, display: "inline-block" }} />
              ))}
              {fmt(domain[1])}
            </div>
          )}
          {hover && (
            <div className="note">
              <strong>{hover.geography_name}</strong>: {fmt(hover.value)} ·{" "}
              {hover.transactions} tx · reliability {hover.reliability_grade}
            </div>
          )}
        </div>

        <div className="card">
          <strong>All regions</strong>
          <table className="data">
            <thead>
              <tr><th>Region</th><th>{METRICS.find((m) => m.id === metric)?.label}</th>
                  <th>Tx</th><th>Grade</th></tr>
            </thead>
            <tbody>
              {[...cells]
                .sort((a, b) => (b.value ?? -1e9) - (a.value ?? -1e9))
                .map((c) => (
                  <tr key={c.geography_id}>
                    <td>{c.geography_name}</td>
                    <td>{fmt(c.value)}</td>
                    <td>{c.transactions}</td>
                    <td>{c.reliability_grade}</td>
                  </tr>
                ))}
            </tbody>
          </table>
        </div>
      </div>
    </>
  );
}
