/**
 * =============================================================================
 * QWHPI — Quebec Weekly Housing Price Index
 * Author  : Simon-Pierre Boucher
 * Contact : contact@spboucher.ai
 * File    : web/components/IndexChart.tsx
 * Purpose : Weekly index chart — smoothed line, 95% CI band, optional raw
 *           overlay, index-points or dollar-value unit, volume subchart.
 * =============================================================================
 */
"use client";

import {
  Area,
  Bar,
  BarChart,
  Brush,
  CartesianGrid,
  ComposedChart,
  Line,
  ReferenceLine,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from "recharts";
import type { Observation } from "../lib/api";
import type { MarketEvent } from "../lib/events";

export type ChartUnit = "points" | "dollars";

interface Props {
  observations: Observation[];
  showRaw?: boolean;
  showVolume?: boolean;
  unit?: ChartUnit;
  annotations?: MarketEvent[];
  showBrush?: boolean;
}

interface Point {
  period: string;
  smoothed: number;
  raw: number | null;
  band: [number, number];
  transactions: number;
  partial: boolean;
  mom: number | null;
}

function money(v: number): string {
  return "$" + Math.round(v).toLocaleString("en-CA");
}

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

function fmtVal(v: number | null | undefined, unit: ChartUnit): string {
  if (v == null || Number.isNaN(v)) return "—";
  return unit === "dollars" ? money(v) : v.toFixed(1);
}

function ChartTooltip({ active, payload, label, unit }: {
  active?: boolean;
  payload?: ReadonlyArray<{ payload?: unknown }>;
  label?: unknown;
  unit: ChartUnit;
}) {
  const p = payload?.[0]?.payload as Point | undefined;
  if (!active || !p) return null;
  return (
    <div className="chart-tooltip">
      <div className="tt-label">
        Month of {String(label)}
        {p.partial ? " · partial" : ""}
      </div>
      <div className="tt-value">
        {fmtVal(p.smoothed, unit)}{" "}
        <span className="tt-ci">
          [{fmtVal(p.band[0], unit)} – {fmtVal(p.band[1], unit)}]
        </span>
      </div>
      {p.mom != null && (
        <div className={p.mom >= 0 ? "delta up" : "delta down"}
             style={{ fontSize: 12.5 }}>
          {p.mom >= 0 ? "▲" : "▼"} {Math.abs(p.mom).toFixed(2)}% vs previous month
        </div>
      )}
      {p.raw != null && (
        <div className="tt-raw">raw monthly: {fmtVal(p.raw, unit)}</div>
      )}
      <div className="tt-n">{p.transactions} transactions</div>
    </div>
  );
}

export default function IndexChart({
  observations,
  showRaw = false,
  showVolume = true,
  unit = "points",
  annotations = [],
  showBrush = false,
}: Props) {
  // Fixed-basket conversion: representative_value = basket × index/100, so
  // one constant per cell converts every variant (raw, band) to dollars.
  const withRep = observations.find(
    (o) => o.representative_value != null && o.index_smoothed > 0,
  );
  const basket =
    unit === "dollars" && withRep
      ? (withRep.representative_value as number) / withRep.index_smoothed
      : 1 / 100;
  const k = unit === "dollars" ? basket : 1;

  const data: Point[] = observations.map((o, i) => ({
    period: o.period,
    smoothed: o.index_smoothed * (unit === "dollars" ? k : 1),
    raw: o.index == null ? null : o.index * (unit === "dollars" ? k : 1),
    band: [
      o.lower_95 * (unit === "dollars" ? k : 1),
      o.upper_95 * (unit === "dollars" ? k : 1),
    ],
    transactions: o.transactions,
    partial: o.is_partial_month,
    mom: i > 0
      ? (o.index_smoothed / observations[i - 1].index_smoothed - 1) * 100
      : null,
  }));

  const visibleEvents = annotations.filter((e) =>
    data.some((d) => d.period === e.period));

  // Scale to the index line, not the CI band: the first thin weeks carry
  // huge honest intervals that would otherwise crush the whole chart.
  const lineVals = data.flatMap((d) =>
    d.raw != null && showRaw ? [d.smoothed, d.raw] : [d.smoothed],
  );
  const lo = Math.min(...lineVals);
  const hi = Math.max(...lineVals);
  const pad = (hi - lo) * 0.12 || 5;
  const yDomain: [number, number] = [lo - pad, hi + pad];

  const tickFmt = (v: number) =>
    unit === "dollars" ? moneyCompact(v) : String(Math.round(v));

  return (
    <div>
      <div style={{ width: "100%", height: showBrush ? 396 : 340 }}
           role="img"
           aria-label={`Monthly index chart, ${data.length} observations with 95% confidence band`}>
        <ResponsiveContainer>
          <ComposedChart data={data} margin={{ top: 22, right: 12, left: 4, bottom: 0 }}>
            <CartesianGrid stroke="var(--border)" strokeDasharray="2 4" vertical={false} />
            <XAxis
              dataKey="period"
              tick={{ fill: "var(--text-muted)", fontSize: 12 }}
              tickLine={false}
              axisLine={{ stroke: "var(--border)" }}
              minTickGap={70}
            />
            <YAxis
              domain={yDomain}
              allowDataOverflow
              tickFormatter={tickFmt}
              tick={{ fill: "var(--text-muted)", fontSize: 12 }}
              tickLine={false}
              axisLine={false}
              width={unit === "dollars" ? 58 : 46}
            />
            <Tooltip
              content={(p) => <ChartTooltip {...p} unit={unit} />}
              cursor={{ stroke: "var(--text-muted)", strokeWidth: 1 }}
            />
            <Area
              dataKey="band"
              stroke="none"
              fill="var(--band)"
              isAnimationActive={false}
              name="95% CI"
            />
            {showRaw && (
              <Line
                dataKey="raw"
                stroke="var(--series-2)"
                strokeWidth={1}
                dot={false}
                connectNulls
                isAnimationActive={false}
                name="Raw monthly"
              />
            )}
            <Line
              dataKey="smoothed"
              stroke="var(--series-1)"
              strokeWidth={2}
              dot={false}
              isAnimationActive
              animationDuration={700}
              animationEasing="ease-out"
              name={unit === "dollars" ? "Representative value" : "Index (smoothed)"}
            />
            {visibleEvents.map((e) => (
              <ReferenceLine
                key={e.period}
                x={e.period}
                stroke="var(--text-muted)"
                strokeDasharray="4 4"
                strokeWidth={1}
                label={{
                  value: e.short,
                  position: "top",
                  fill: "var(--text-muted)",
                  fontSize: 9.5,
                }}
              />
            ))}
            {showBrush && (
              <Brush
                dataKey="period"
                height={26}
                travellerWidth={8}
                stroke="var(--border-strong)"
                fill="var(--surface-2)"
              />
            )}
          </ComposedChart>
        </ResponsiveContainer>
      </div>
      {showRaw && (
        <div className="note" style={{ display: "flex", gap: 16 }}>
          <span><span style={{ color: "var(--series-1)" }}>―</span>{" "}
            {unit === "dollars" ? "Representative value (smoothed)" : "Index (one-sided smoothed)"}</span>
          <span><span style={{ color: "var(--series-2)" }}>―</span> Raw monthly estimate</span>
          <span style={{ color: "var(--text-muted)" }}>▮ 95% CI</span>
        </div>
      )}
      {showVolume && (
        <div style={{ width: "100%", height: 90, marginTop: 8 }}>
          <ResponsiveContainer>
            <BarChart data={data} margin={{ top: 0, right: 12, left: 4, bottom: 0 }}>
              <XAxis dataKey="period" hide />
              <YAxis
                tick={{ fill: "var(--text-muted)", fontSize: 11 }}
                tickLine={false}
                axisLine={false}
                width={unit === "dollars" ? 58 : 46}
              />
              <Tooltip
                content={({ active, payload, label }) =>
                  active && payload?.length ? (
                    <div className="chart-tooltip">
                      {label}: {(payload[0].payload as Point).transactions} transactions
                    </div>
                  ) : null
                }
              />
              <Bar
                dataKey="transactions"
                fill="var(--series-1)"
                opacity={0.55}
                isAnimationActive={false}
                name="Transactions"
              />
            </BarChart>
          </ResponsiveContainer>
        </div>
      )}
    </div>
  );
}
