/**
 * =============================================================================
 * QWHPI — Quebec Weekly Housing Price Index
 * Author  : Simon-Pierre Boucher
 * Contact : contact@spboucher.ai
 * File    : web/app/compare/page.tsx
 * Purpose : Compare — multi-series chart in index points, dollar values, or
 *           rebased to a common week (max 5 series, fixed hue order).
 * =============================================================================
 */
"use client";

import { Suspense, useEffect, useMemo, useState } from "react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import {
  CartesianGrid,
  Line,
  LineChart,
  Legend,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from "recharts";
import {
  fetchGeographies,
  fetchSeries,
  reportUrl,
  type GeographyNode,
  type Observation,
} from "../../lib/api";

const SERIES_COLORS = [
  "var(--series-1)", "var(--series-2)", "var(--series-3)",
  "var(--series-4)", "var(--series-5)",
];
const TYPES = ["all", "unifamilial", "condo", "plex"];
const DEFAULT = ["montreal:condo", "quebec-city:condo", "gatineau:condo"];

type Mode = "index" | "dollars" | "rebased";

function moneyCompact(v: number): string {
  return v >= 1_000_000
    ? "$" + (v / 1_000_000).toFixed(2) + "M"
    : "$" + Math.round(v / 1000) + "k";
}

function CompareInner() {
  const params = useSearchParams();
  const router = useRouter();
  const pathname = usePathname();
  const initSeries = params.get("series")?.split(",").filter(Boolean);
  const [geos, setGeos] = useState<GeographyNode[]>([]);
  const [picked, setPicked] = useState<string[]>(
    initSeries?.length ? initSeries.slice(0, 5) : DEFAULT);
  const [hidden, setHidden] = useState<Set<string>>(new Set());
  const [pendingGeo, setPendingGeo] = useState("laval");
  const [pendingType, setPendingType] = useState("condo");
  const [mode, setMode] = useState<Mode>(
    (["index", "dollars", "rebased"] as readonly string[]).includes(
      params.get("mode") ?? "") ? (params.get("mode") as Mode) : "index");
  const [rebase, setRebase] = useState<string>(params.get("rebase") ?? "2022-01");
  const [obs, setObs] = useState<Record<string, Observation[]>>({});
  const [error, setError] = useState<string | null>(null);

  // Shareable URL
  useEffect(() => {
    const q = new URLSearchParams({ series: picked.join(",") });
    if (mode !== "index") q.set("mode", mode);
    if (mode === "rebased") q.set("rebase", rebase);
    router.replace(`${pathname}?${q.toString()}`, { scroll: false });
  }, [picked, mode, rebase, router, pathname]);

  useEffect(() => {
    fetchGeographies()
      .then((g) => setGeos(g.published_series))
      .catch((e) => setError(String(e)));
  }, []);

  useEffect(() => {
    let alive = true;
    Promise.all(
      picked.map(async (p) => {
        const [g, t] = p.split(":");
        return [p, (await fetchSeries(g, t)).observations] as const;
      }),
    )
      .then((pairs) => {
        if (!alive) return;
        setObs(Object.fromEntries(pairs));
        setError(null);
      })
      .catch((e) => setError(String(e)));
    return () => { alive = false; };
  }, [picked]);

  const rows = useMemo(() => {
    const byWeek: Record<string, Record<string, unknown>> = {};
    for (const [name, series] of Object.entries(obs)) {
      if (!picked.includes(name)) continue;
      let baseVal = 1;
      if (mode === "rebased") {
        const b = series.find((o) => o.period === rebase);
        if (!b) continue;
        baseVal = b.index_smoothed;
      }
      for (const o of series) {
        const v =
          mode === "dollars"
            ? o.representative_value
            : mode === "rebased"
              ? (o.index_smoothed / baseVal) * 100
              : o.index_smoothed;
        if (v == null) continue;
        (byWeek[o.period] ??= { week: o.period })[name] = Math.round(v * 100) / 100;
      }
    }
    return Object.values(byWeek).sort((a, b) =>
      String(a.week).localeCompare(String(b.week)));
  }, [obs, picked, mode, rebase]);

  const nameOf = useMemo(() => {
    const m = new Map(geos.map((g) => [g.geography_id, g.geography_name]));
    return (spec: string) => {
      const [g, t] = spec.split(":");
      return `${m.get(g) ?? g} · ${t}`;
    };
  }, [geos]);

  const add = () => {
    const spec = `${pendingGeo}:${pendingType}`;
    if (!picked.includes(spec) && picked.length < 5) setPicked([...picked, spec]);
  };

  return (
    <>
      <h1>Compare series</h1>
      <p className="lede">
        Up to five series; colors keep their series when you add or remove.
        View index levels, dollar values, or rebase to a common month.
      </p>
      <div className="controls">
        <select value={pendingGeo} onChange={(e) => setPendingGeo(e.target.value)}
                aria-label="Geography to add">
          {geos.map((g) => (
            <option key={g.geography_id} value={g.geography_id}>
              {g.geography_name}
            </option>
          ))}
        </select>
        <select value={pendingType} onChange={(e) => setPendingType(e.target.value)}
                aria-label="Property type to add">
          {TYPES.map((t) => <option key={t} value={t}>{t}</option>)}
        </select>
        <button className="ctrl" onClick={add} disabled={picked.length >= 5}>
          Add series
        </button>
        <div className="seg" role="group" aria-label="Comparison mode">
          <button aria-pressed={mode === "index"} onClick={() => setMode("index")}>
            Index
          </button>
          <button aria-pressed={mode === "dollars"} onClick={() => setMode("dollars")}>
            $ value
          </button>
          <button aria-pressed={mode === "rebased"} onClick={() => setMode("rebased")}>
            Rebased
          </button>
        </div>
        {mode === "rebased" && (
          <input
            type="month"
            value={rebase}
            aria-label="Rebase month"
            onChange={(e) => setRebase(e.target.value)}
          />
        )}
        {picked.length >= 1 && (
          <a className="primary" href={reportUrl(picked)} target="_blank"
             rel="noreferrer">
            ⤓ Comparison report (PDF)
          </a>
        )}
      </div>
      <div className="controls">
        {picked.map((s) => (
          <button
            key={s}
            className="chip"
            aria-pressed={!hidden.has(s)}
            style={{ opacity: hidden.has(s) ? 0.4 : 1 }}
            onClick={() =>
              setHidden((h) => {
                const n = new Set(h);
                if (n.has(s)) n.delete(s);
                else n.add(s);
                return n;
              })}
            title={hidden.has(s) ? "Show series" : "Hide series"}
          >
            <span style={{ color: SERIES_COLORS[picked.indexOf(s)] }}>●</span>{" "}
            {nameOf(s)}
            <span
              role="button"
              aria-label={`Remove ${nameOf(s)}`}
              style={{ marginLeft: 4, color: "var(--text-muted)" }}
              onClick={(e) => {
                e.stopPropagation();
                setPicked(picked.filter((p) => p !== s));
              }}
            >✕</span>
          </button>
        ))}
        <span className="note">click a chip to toggle · ✕ removes</span>
      </div>

      {error && <div className="card"><span className="note">{error}</span></div>}

      <div className="card" style={{ height: 440 }}>
        <ResponsiveContainer>
          <LineChart data={rows} margin={{ top: 8, right: 12, left: 4, bottom: 0 }}>
            <CartesianGrid stroke="var(--border)" strokeDasharray="2 4" vertical={false} />
            <XAxis
              dataKey="week"
              tick={{ fill: "var(--text-muted)", fontSize: 12 }}
              tickLine={false}
              axisLine={{ stroke: "var(--border)" }}
              minTickGap={70}
            />
            <YAxis
              domain={["auto", "auto"]}
              tickFormatter={(v: number) =>
                mode === "dollars" ? moneyCompact(v) : String(Math.round(v))}
              tick={{ fill: "var(--text-muted)", fontSize: 12 }}
              tickLine={false}
              axisLine={false}
              width={mode === "dollars" ? 58 : 48}
            />
            <Tooltip
              contentStyle={{
                background: "var(--surface-1)",
                border: "1px solid var(--border-strong)",
                borderRadius: 10, fontSize: 13,
              }}
              labelStyle={{ color: "var(--text-secondary)" }}
              formatter={(v: number, name: string) => [
                mode === "dollars"
                  ? "$" + Math.round(v).toLocaleString("en-CA")
                  : v.toFixed(1),
                nameOf(name),
              ]}
            />
            <Legend
              formatter={(v: string) => (
                <span style={{ color: "var(--text-secondary)", fontSize: 13 }}>
                  {nameOf(v)}
                </span>
              )}
            />
            {picked.map((s, i) => (
              <Line
                key={s}
                dataKey={s}
                stroke={SERIES_COLORS[i]}
                strokeWidth={2}
                dot={false}
                hide={hidden.has(s)}
                isAnimationActive
                animationDuration={700}
                animationEasing="ease-out"
                connectNulls
              />
            ))}
          </LineChart>
        </ResponsiveContainer>
      </div>
      <p className="note">
        {mode === "rebased"
          ? `All series = 100 at ${rebase}.`
          : mode === "dollars"
            ? "Representative dollar value per series (fixed 2021 basket carried by the index)."
            : "Levels shown on each series' own 2021 = 100 base."}
      </p>
    </>
  );
}

export default function Compare() {
  return (
    <Suspense fallback={null}>
      <CompareInner />
    </Suspense>
  );
}
