/**
 * =============================================================================
 * QWHPI — Quebec Weekly Housing Price Index
 * Author  : Simon-Pierre Boucher
 * Contact : contact@spboucher.ai
 * File    : web/app/explore/page.tsx
 * Purpose : Explore — series picker, index chart with CI band, raw overlay
 *           toggle, growth horizons, volume subchart, CSV download.
 * =============================================================================
 */
"use client";

import { Suspense, useEffect, useMemo, useRef, useState } from "react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import {
  csvUrl,
  fetchGeographies,
  fetchSeries,
  fetchStats,
  reportUrl,
  type GeographyNode,
  type SeriesResponse,
  type SeriesStats,
} from "../../lib/api";
import dynamic from "next/dynamic";
import type { ChartUnit } from "../../components/IndexChart";
import { ChartSkeleton, DashboardSkeleton } from "../../components/Skeleton";
import DataTable from "../../components/DataTable";
import { MARKET_EVENTS } from "../../lib/events";
import { exportChartPng } from "../../lib/exportPng";

const IndexChart = dynamic(() => import("../../components/IndexChart"), {
  ssr: false,
  loading: () => <ChartSkeleton height={430} />,
});
import ReliabilityBadge from "../../components/ReliabilityBadge";

const TYPES = ["all", "unifamilial", "condo", "plex"];
const RANGES = ["1y", "3y", "5y", "ytd", "max"] as const;
type Range = (typeof RANGES)[number];

function rangeCutoff(range: Range, lastPeriod: string): string | null {
  if (range === "max") return null;
  const [y, m] = lastPeriod.split("-").map(Number);
  if (range === "ytd") return `${y}-01`;
  const months = range === "1y" ? 12 : range === "3y" ? 36 : 60;
  const total = y * 12 + (m - 1) - months;
  const cy = Math.floor(total / 12);
  const cm = (total % 12) + 1;
  return `${cy}-${String(cm).padStart(2, "0")}`;
}
const HORIZONS: { key: keyof HorizonRow; label: string }[] = [
  { key: "monthly_pct", label: "1m" },
  { key: "three_month_pct", label: "3m" },
  { key: "six_month_pct", label: "6m" },
  { key: "yoy_pct", label: "YoY" },
];
type HorizonRow = {
  monthly_pct: number | null;
  three_month_pct: number | null;
  six_month_pct: number | null;
  yoy_pct: number | null;
};

function ExploreInner() {
  const params = useSearchParams();
  const router = useRouter();
  const pathname = usePathname();
  const [geos, setGeos] = useState<GeographyNode[]>([]);
  const [geo, setGeo] = useState(params.get("geography") ?? "quebec");
  const [ptype, setPtype] = useState(params.get("type") ?? "all");
  const [series, setSeries] = useState<SeriesResponse | null>(null);
  const [stats, setStats] = useState<SeriesStats | null>(null);
  const [showRaw, setShowRaw] = useState(false);
  const [unit, setUnit] = useState<ChartUnit>(
    params.get("unit") === "dollars" ? "dollars" : "points");
  const [range, setRange] = useState<Range>(
    (RANGES as readonly string[]).includes(params.get("range") ?? "")
      ? (params.get("range") as Range) : "max");
  const [showEvents, setShowEvents] = useState(params.get("events") === "1");
  const [showTable, setShowTable] = useState(false);
  const chartRef = useRef<HTMLDivElement>(null);
  const [error, setError] = useState<string | null>(null);

  // Shareable URL: selection encoded in query params.
  useEffect(() => {
    const q = new URLSearchParams({ geography: geo, type: ptype });
    if (unit === "dollars") q.set("unit", "dollars");
    if (range !== "max") q.set("range", range);
    if (showEvents) q.set("events", "1");
    router.replace(`${pathname}?${q.toString()}`, { scroll: false });
  }, [geo, ptype, unit, range, showEvents, router, pathname]);

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

  useEffect(() => {
    setSeries(null);
    setStats(null);
    fetchSeries(geo, ptype)
      .then(setSeries)
      .catch((e) => setError(String(e)));
    fetchStats(geo, ptype).then(setStats).catch(() => setStats(null));
  }, [geo, ptype]);

  const grouped = useMemo(() => {
    const by: Record<string, GeographyNode[]> = {};
    for (const g of geos) (by[g.geography_level] ??= []).push(g);
    return by;
  }, [geos]);

  if (error)
    return (
      <div className="card" style={{ marginTop: 32 }}>
        <strong>API unreachable.</strong> <span className="note">{error}</span>
      </div>
    );

  const last = series?.observations.filter((o) => !o.is_partial_month).at(-1);

  const visibleObs = useMemo(() => {
    if (!series) return [];
    const obs = series.observations;
    const cutoff = obs.length
      ? rangeCutoff(range, obs[obs.length - 1].period) : null;
    return cutoff ? obs.filter((o) => o.period >= cutoff) : obs;
  }, [series, range]);

  return (
    <>
      <h1>Explore a series</h1>
      <div className="controls">
        <label htmlFor="geo">Geography</label>
        <select id="geo" value={geo} onChange={(e) => setGeo(e.target.value)}>
          {(["province", "region", "municipality"] as const).map((lvl) => (
            <optgroup key={lvl} label={lvl}>
              {(grouped[lvl] ?? []).map((g) => (
                <option key={g.geography_id} value={g.geography_id}>
                  {g.geography_name}
                </option>
              ))}
            </optgroup>
          ))}
        </select>
        <label htmlFor="ptype">Type</label>
        <select id="ptype" value={ptype} onChange={(e) => setPtype(e.target.value)}>
          {TYPES.map((t) => (
            <option key={t} value={t}>{t}</option>
          ))}
        </select>
        <div className="seg" role="group" aria-label="Chart unit">
          <button aria-pressed={unit === "points"} onClick={() => setUnit("points")}>
            Index
          </button>
          <button aria-pressed={unit === "dollars"} onClick={() => setUnit("dollars")}>
            $ value
          </button>
        </div>
        <div className="seg" role="group" aria-label="Period range">
          {RANGES.map((r) => (
            <button key={r} aria-pressed={range === r} onClick={() => setRange(r)}>
              {r.toUpperCase()}
            </button>
          ))}
        </div>
        <button
          className="ctrl"
          aria-pressed={showRaw}
          onClick={() => setShowRaw((v) => !v)}
        >
          Raw monthly overlay
        </button>
        <button
          className="ctrl"
          aria-pressed={showEvents}
          onClick={() => setShowEvents((v) => !v)}
        >
          Events
        </button>
        <button
          className="ctrl"
          onClick={() =>
            chartRef.current &&
            exportChartPng(chartRef.current, `qhpi_${geo}_${ptype}.png`)}
        >
          ⤓ PNG
        </button>
        <a className="ctrl" href={csvUrl(geo, ptype)}>
          ⤓ CSV
        </a>
        <a className="primary" href={reportUrl([`${geo}:${ptype}`])}
           target="_blank" rel="noreferrer">
          ⤓ PDF report
        </a>
      </div>

      {!series ? (
        <DashboardSkeleton />
      ) : (
        <>
          <div className="card fade-up">
            <div style={{ display: "flex", justifyContent: "space-between", flexWrap: "wrap", gap: 8 }}>
              <strong>
                {series.geography_name} · {series.property_type} ·{" "}
                {unit === "dollars"
                  ? "representative dollar value"
                  : "base 2021 = 100"}
              </strong>
              {last && <ReliabilityBadge grade={last.reliability_grade} />}
            </div>
            {last && (last.reliability_grade === "D" || last.reliability_grade === "E") && (
              <p className="low-reliability-note">
                Thin market: monthly movements are heavily shrunk toward the
                parent trend. Read levels and multi-month changes, not
                single months.
              </p>
            )}
            <div ref={chartRef}>
              <IndexChart observations={visibleObs} showRaw={showRaw}
                          unit={unit}
                          annotations={showEvents ? MARKET_EVENTS : []}
                          showBrush={range === "max"} />
            </div>
            <div className="controls" style={{ margin: "8px 0 0" }}>
              <button className="ctrl" aria-pressed={showTable}
                      aria-expanded={showTable}
                      onClick={() => setShowTable((v) => !v)}>
                {showTable ? "Hide data table" : "Data table"}
              </button>
            </div>
            {showTable && <DataTable observations={visibleObs} />}
          </div>

          {last && (
            <>
              <h2>Growth (month of {last.period})</h2>
              <div className="grid cols-4">
                {HORIZONS.map(({ key, label }) => {
                  const v = last[key];
                  return (
                    <div className="card stat-tile" key={key}>
                      <span className="label">{label}</span>
                      <span
                        className={
                          "value " + (v != null && v < 0 ? "delta down" : "delta up")
                        }
                        style={{ fontSize: 22 }}
                      >
                        {v == null ? "—" : `${v > 0 ? "+" : ""}${v.toFixed(2)}%`}
                      </span>
                    </div>
                  );
                })}
                <div className="card stat-tile">
                  <span className="label">Representative value</span>
                  <span className="value" style={{ fontSize: 22 }}>
                    {last.representative_value
                      ? `$${Math.round(last.representative_value).toLocaleString()}`
                      : "—"}
                  </span>
                  <span className="note">
                    n={last.transactions} · eff. N≈
                    {last.effective_sample_size?.toFixed(0) ?? "—"}
                  </span>
                </div>
              </div>
              {stats && (
                <>
                  <h2>Structure &amp; risk</h2>
                  <div className="grid cols-8">
                    {([
                      ["Since 2021", `${stats.since_2021_pct > 0 ? "+" : ""}${stats.since_2021_pct.toFixed(1)}%`],
                      ["CAGR", `${stats.cagr_pct > 0 ? "+" : ""}${stats.cagr_pct.toFixed(2)}%`],
                      ["Peak", `${stats.peak_index.toFixed(1)} (${stats.peak_period})`],
                      ["Vs peak", stats.at_record_high ? "★ at peak" : `${stats.drawdown_pct.toFixed(2)}%`],
                      ["Volatility 12m", `${stats.volatility_12m_pct.toFixed(2)}%`],
                      ["Momentum 3m ann.", `${stats.momentum_3m_ann_pct > 0 ? "+" : ""}${stats.momentum_3m_ann_pct.toFixed(1)}%`],
                      ["YoY rank", stats.yoy_rank ? `${stats.yoy_rank}/${stats.yoy_rank_of}` : "—"],
                      ["Sales 12m", stats.volume_12m.toLocaleString()],
                      ["Volume YoY", stats.volume_yoy_pct == null ? "—" : `${stats.volume_yoy_pct > 0 ? "+" : ""}${stats.volume_yoy_pct.toFixed(1)}%`],
                      ["$ volume 12m", `$${(stats.dollar_volume_12m / 1e9).toFixed(2)}B`],
                      ["Assessment gap", stats.assessment_gap == null ? "—" : `${stats.assessment_gap.toFixed(2)}×`],
                      ["Effective N", stats.effective_sample_size?.toFixed(0) ?? "—"],
                    ] as [string, string][]).map(([label, val]) => (
                      <div className="card stat-tile mini-tile" key={label}>
                        <span className="label">{label}</span>
                        <span className="value" style={{ fontSize: 17 }}>{val}</span>
                      </div>
                    ))}
                  </div>
                </>
              )}
            </>
          )}
        </>
      )}
    </>
  );
}

export default function Explore() {
  return (
    <Suspense fallback={<DashboardSkeleton />}>
      <ExploreInner />
    </Suspense>
  );
}
