import { Area, AreaChart, ReferenceArea, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
import type { Lap } from "../../types/api";

function paceSecPerKm(mps: number | null): number | null {
  if (mps == null || mps <= 0) return null;
  return 1000 / mps;
}

function formatPaceShort(secPerKm: number): string {
  const m = Math.floor(secPerKm / 60);
  const s = Math.round(secPerKm % 60);
  return `${m}:${s.toString().padStart(2, "0")}`;
}

function formatPace(secPerKm: number): string {
  return `${formatPaceShort(secPerKm)}/km`;
}

function formatElapsed(minutes: number): string {
  const m = Math.floor(minutes);
  const s = Math.round((minutes - m) * 60);
  return `${m}:${s.toString().padStart(2, "0")}`;
}

// Recharts' "dataMin - N" domain expressions don't combine reliably with
// reversed axes, so the padded numeric domain is computed directly instead.
function paddedDomain(values: Array<number | null | undefined>, pad: number): [number, number] {
  const nums = values.filter((v): v is number => v != null);
  if (nums.length === 0) return [0, 1];
  return [Math.min(...nums) - pad, Math.max(...nums) + pad];
}

// Phase a lap represents, by Garmin's own per-lap IntensityType tagging.
// Colors distinguish warm-up / effort / recovery / cool-down bands behind
// the pace/HR trace so a run's structure reads at a glance.
const PHASE_COLORS: Record<string, string> = {
  WARMUP: "#f59e0b",
  ACTIVE: "#ef4444",
  REST: "#3b82f6",
  RECOVERY: "#3b82f6",
  COOLDOWN: "#a855f7",
};
const PHASE_LABELS: Record<string, string> = {
  WARMUP: "Warm-up",
  ACTIVE: "Effort",
  REST: "Recovery",
  RECOVERY: "Recovery",
  COOLDOWN: "Cool-down",
};

interface Point {
  t: number;
  actualPace: number | null;
  targetPaceRange: [number, number] | null;
  actualHR: number | null;
  targetHRRange: [number, number] | null;
}

function rangeTooltipFormatter(rangeLabel: string, formatValue: (v: number) => string) {
  return (value: unknown, name: unknown): [string, string] => {
    if (Array.isArray(value)) return [`${formatValue(value[0])}–${formatValue(value[1])}`, rangeLabel];
    return [formatValue(Number(value)), String(name)];
  };
}

// Shows each lap's actual pace/HR over elapsed time for every activity that
// has laps, with the workout's warm-up/effort/recovery/cool-down structure
// shaded behind it. The expected band from a structured Garmin workout (see
// backend's alignWorkoutTargets) is layered on top when available -- most
// activities aren't from a structured workout with target zones, so absence
// of that band is the common case, not an error.
export function ExpectedVsActualChart({ laps }: { laps: Lap[] }) {
  if (laps.length === 0) return null;

  const hasPaceTarget = laps.some((l) => l.TargetPaceLowMps != null && l.TargetPaceHighMps != null);
  const hasHRTarget = laps.some((l) => l.TargetHRLowBpm != null && l.TargetHRHighBpm != null);

  const points: Point[] = [];
  const phaseBands: Array<{ x1: number; x2: number; color: string }> = [];
  let elapsedMin = 0;
  for (const l of laps) {
    const start = elapsedMin;
    elapsedMin += l.DurationSeconds / 60;

    // Pace (sec/km) is inverted vs speed (m/s): the *faster* (higher) speed
    // bound is the *lower* (faster) pace bound.
    const paceLow = l.TargetPaceHighMps != null ? paceSecPerKm(l.TargetPaceHighMps) : null;
    const paceHigh = l.TargetPaceLowMps != null ? paceSecPerKm(l.TargetPaceLowMps) : null;

    points.push({
      t: start,
      actualPace: paceSecPerKm(l.AvgSpeedMps),
      targetPaceRange: paceLow != null && paceHigh != null ? [paceLow, paceHigh] : null,
      actualHR: l.AvgHR,
      targetHRRange: l.TargetHRLowBpm != null && l.TargetHRHighBpm != null ? [l.TargetHRLowBpm, l.TargetHRHighBpm] : null,
    });

    const color = PHASE_COLORS[l.IntensityType];
    if (color) phaseBands.push({ x1: start, x2: elapsedMin, color });
  }
  // Closing point so the last lap's step extends visually to the end.
  points.push({ ...points[points.length - 1], t: elapsedMin });

  const paceDomain = paddedDomain(points.flatMap((p) => [p.actualPace, ...(p.targetPaceRange ?? [])]), 10);
  const hrDomain = paddedDomain(points.flatMap((p) => [p.actualHR, ...(p.targetHRRange ?? [])]), 5);
  const phasesPresent = [...new Set(laps.map((l) => l.IntensityType).filter((t) => PHASE_COLORS[t]))];

  const paceTooltipFormatter = rangeTooltipFormatter("Target range", (v) => `${formatPace(v)}`);
  const hrTooltipFormatter = rangeTooltipFormatter("Target range", (v) => `${Math.round(v)} bpm`);

  return (
    <div className="expected-actual-charts">
      {phasesPresent.length > 0 && (
        <div className="phase-legend">
          {phasesPresent.map((phase) => (
            <span key={phase} className="phase-legend-item">
              <span className="phase-legend-swatch" style={{ background: PHASE_COLORS[phase] }} />
              {PHASE_LABELS[phase] ?? phase}
            </span>
          ))}
        </div>
      )}
      <div className="mini-chart">
        <span className="mini-chart-label">Pace{hasPaceTarget ? " vs target" : ""}</span>
        <ResponsiveContainer width="100%" height={100}>
          <AreaChart data={points} margin={{ top: 4, right: 4, bottom: 0, left: 4 }}>
            {phaseBands.map((b, i) => (
              <ReferenceArea key={i} x1={b.x1} x2={b.x2} fill={b.color} fillOpacity={0.12} strokeOpacity={0} />
            ))}
            <XAxis dataKey="t" type="number" domain={[0, elapsedMin]} tick={{ fontSize: 10 }} tickFormatter={(v) => `${Math.round(Number(v))}m`} />
            <YAxis reversed domain={paceDomain} tick={{ fontSize: 10 }} width={34} tickFormatter={(v) => formatPaceShort(Number(v))} />
            <Tooltip formatter={paceTooltipFormatter} labelFormatter={(t) => `${formatElapsed(Number(t))} elapsed`} contentStyle={{ fontSize: 12 }} />
            {hasPaceTarget && (
              <Area type="stepAfter" dataKey="targetPaceRange" stroke="none" fill="#9aa0ab" fillOpacity={0.25} isAnimationActive={false} name="Target range" connectNulls />
            )}
            <Area type="monotone" dataKey="actualPace" stroke="#3b82f6" strokeWidth={2} fill="#3b82f6" fillOpacity={0.3} dot={{ r: 2 }} isAnimationActive={false} name="Actual" connectNulls />
          </AreaChart>
        </ResponsiveContainer>
      </div>
      <div className="mini-chart">
        <span className="mini-chart-label">HR{hasHRTarget ? " vs target" : ""}</span>
        <ResponsiveContainer width="100%" height={100}>
          <AreaChart data={points} margin={{ top: 4, right: 4, bottom: 0, left: 4 }}>
            {phaseBands.map((b, i) => (
              <ReferenceArea key={i} x1={b.x1} x2={b.x2} fill={b.color} fillOpacity={0.12} strokeOpacity={0} />
            ))}
            <XAxis dataKey="t" type="number" domain={[0, elapsedMin]} tick={{ fontSize: 10 }} tickFormatter={(v) => `${Math.round(Number(v))}m`} />
            <YAxis domain={hrDomain} tick={{ fontSize: 10 }} width={30} tickFormatter={(v) => String(Math.round(Number(v)))} />
            <Tooltip formatter={hrTooltipFormatter} labelFormatter={(t) => `${formatElapsed(Number(t))} elapsed`} contentStyle={{ fontSize: 12 }} />
            {hasHRTarget && (
              <Area type="stepAfter" dataKey="targetHRRange" stroke="none" fill="#9aa0ab" fillOpacity={0.25} isAnimationActive={false} name="Target range" connectNulls />
            )}
            <Area type="monotone" dataKey="actualHR" stroke="#ef4444" strokeWidth={2} fill="#ef4444" fillOpacity={0.3} dot={{ r: 2 }} isAnimationActive={false} name="Actual" connectNulls />
          </AreaChart>
        </ResponsiveContainer>
      </div>
    </div>
  );
}
