// One observe dashboard chart: fetch lifecycle via the shared useLiveSeries
// hook (view-time fetch keyed on region/dashboard/chart/window + retry,
// cancel-on-unmount). One <Line> per response line — color from the line's
// colorKey (stable per component across charts) — plotted over a numeric
// time axis so every sample keeps its own x position regardless of label
// granularity. Legend entries toggle their line's visibility; ratio charts
// pin the Y axis to the full 0–100% span.

import { useState } from "react";
import {
  CartesianGrid,
  Legend,
  Line,
  LineChart,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from "recharts";
import { fetchObserveSeries } from "@/lib/api-client";
import type { ObserveSeriesResponse } from "@/lib/api-types";
import type { ObserveChartDef, ObserveUnit } from "@/engine/observe-dashboards";
import type { TrendWindowKey } from "@/engine/trend-window";
import { useLiveSeries } from "@/lib/use-live-series";
import {
  bytesTick,
  CHART_GRID_STROKE,
  CHART_MIN_TICK_GAP,
  chartSeriesColor,
  numTick,
  ratioTick,
  ratioValue,
  utc8Minute,
  utc8Tick,
} from "@/lib/chart";
import { Button } from "@/components/ui/Button";

/** Pure pivot: the union of all lines' timestamps (sorted), each line's
 *  samples under its legend key with the epoch-second `t` as the shared x
 *  key; timestamps a line doesn't cover leave that key absent so recharts
 *  breaks the line instead of interpolating. */
export function observeChartData(
  s: ObserveSeriesResponse,
): Array<Record<string, number | string>> {
  const rows = new Map<number, Record<string, number | string>>();
  for (const line of s.series) {
    for (const p of line.values) {
      const existing = rows.get(p.t);
      if (existing === undefined) {
        rows.set(p.t, { t: p.t, [line.legend]: p.v });
      } else {
        existing[line.legend] = p.v;
      }
    }
  }
  return [...rows.entries()].sort((a, b) => a[0] - b[0]).map(([, row]) => row);
}

function yAxisFormatter(unit: ObserveUnit): (v: number) => string {
  if (unit === "ratio") return ratioTick;
  if (unit === "bytes") return bytesTick;
  return numTick;
}

function valueFormatter(unit: ObserveUnit): (v: number) => string {
  if (unit === "ratio") return ratioValue;
  if (unit === "cores") return (v) => `${v.toFixed(2)} cores`;
  if (unit === "bytes") return bytesTick;
  return (v) => numTick(v);
}

export function ObserveChartCard(props: {
  regionKey: string;
  dashboardId: string;
  chart: ObserveChartDef;
  windowKey: TrendWindowKey;
}) {
  const { regionKey, dashboardId, chart, windowKey } = props;
  const { series, error, retry } = useLiveSeries(
    () => fetchObserveSeries(regionKey, dashboardId, chart.id, windowKey),
    [regionKey, dashboardId, chart.id, windowKey],
  );
  // Legends the user has clicked off; a hidden line keeps its (grayed)
  // legend entry so it can be toggled back.
  const [hidden, setHidden] = useState<ReadonlySet<string>>(new Set());
  const toggleLine = (legend: string): void =>
    setHidden((h) => {
      const next = new Set(h);
      if (next.has(legend)) next.delete(legend);
      else next.add(legend);
      return next;
    });

  const title = <div className="mb-1 text-xs font-medium text-text-secondary">{chart.title}</div>;
  const frame = "rounded-lg border border-surface-2 bg-surface-0 px-4 py-3";

  if (error !== null) {
    return (
      <div className={frame}>
        {title}
        <div className="flex items-center gap-3 py-4 text-xs text-text-muted">
          <span className="text-danger">chart unavailable: {error}</span>
          <Button variant="secondary" size="xs" onClick={retry}>
            retry
          </Button>
        </div>
      </div>
    );
  }
  if (series === null) {
    return (
      <div className={frame}>
        {title}
        <div className="py-4 text-center text-xs text-text-muted">loading chart…</div>
      </div>
    );
  }
  const data = observeChartData(series);
  if (data.length < 2) {
    return (
      <div className={frame}>
        {title}
        <div className="py-4 text-center text-xs text-text-muted">
          Need at least two samples for a trend.
        </div>
      </div>
    );
  }

  const yFmt = yAxisFormatter(chart.unit);
  const vFmt = valueFormatter(chart.unit);
  return (
    <div className={frame}>
      {title}
      <div className="h-48">
        <ResponsiveContainer width="100%" height="100%">
          <LineChart data={data} margin={{ top: 4, right: 8, bottom: 0, left: 0 }}>
            <CartesianGrid strokeDasharray="3 3" stroke={CHART_GRID_STROKE} />
            {/* Numeric time axis: every sample keeps its own x position
                even when consecutive labels render at hour granularity. */}
            <XAxis
              dataKey="t"
              type="number"
              domain={["dataMin", "dataMax"]}
              tickFormatter={(t) => utc8Tick(Number(t))}
              tick={{ fontSize: 10 }}
              minTickGap={CHART_MIN_TICK_GAP}
            />
            <YAxis
              tick={{ fontSize: 10 }}
              tickFormatter={(v) => yFmt(Number(v))}
              width={44}
              // Ratios are 0–100% by construction — pin the full span so
              // charts are visually comparable instead of auto-fitting.
              domain={chart.unit === "ratio" ? [0, 1] : ["auto", "auto"]}
            />
            <Tooltip
              formatter={(value) => vFmt(Number(value))}
              labelFormatter={(t) => utc8Minute(Number(t))}
              contentStyle={{
                background: "var(--surface-0)",
                border: "1px solid var(--surface-2)",
                borderRadius: 6,
                fontSize: 12,
                padding: "6px 8px",
              }}
              labelStyle={{ color: "var(--text-muted)", fontSize: 11, marginBottom: 2 }}
              itemStyle={{ fontSize: 12, padding: 0 }}
              // Highest value first — the busy component reads at the top.
              itemSorter={(item) => -Number(item.value)}
              cursor={{ stroke: "var(--surface-2)", strokeDasharray: "3 3" }}
            />
            <Legend
              wrapperStyle={{ fontSize: 12 }}
              onClick={(e) => {
                if (e && typeof e.dataKey === "string") toggleLine(e.dataKey);
              }}
            />
            {series.series.map((line) => (
              <Line
                key={line.legend}
                type="monotone"
                dataKey={line.legend}
                name={line.legend}
                stroke={chartSeriesColor(line.colorKey)}
                strokeWidth={1.5}
                dot={false}
                isAnimationActive={false}
                hide={hidden.has(line.legend)}
              />
            ))}
          </LineChart>
        </ResponsiveContainer>
      </div>
    </div>
  );
}
