'use client'

import { useEffect, useMemo, useRef, useState } from 'react'
import { Loader2 } from 'lucide-react'

// A check's readings over time, drawn inside its expanded row on /health.
//
// The row above already answers "what is it now". This answers the question you
// actually opened the row to ask — whether 74.6% is where it has always sat or
// where it has been climbing for a fortnight. One series, so no legend: the row
// it lives under names it.

interface Point { t: number; v: number; lo: number; hi: number; s: string }
interface Series { points: Point[]; unit: string | null; hours: number; samples: number }

const RANGES = [
  { label: '24h', hours: 24 },
  { label: '7d', hours: 168 },
  { label: '30d', hours: 720 },
]

// viewBox units. The height includes the x-axis band on purpose — sizing the box
// to the plot alone is what gives a card its own little scrollbar.
const W = 600, H = 150
const PAD = { top: 10, right: 46, bottom: 22, left: 34 }
const PLOT = { w: W - PAD.left - PAD.right, h: H - PAD.top - PAD.bottom }

function fmt(v: number, unit: string | null): string {
  const n = Math.abs(v) >= 100 ? Math.round(v) : Math.round(v * 10) / 10
  return `${n}${unit && unit !== '' ? unit : ''}`
}

function axisTicks(lo: number, hi: number): number[] {
  const span = hi - lo
  if (span <= 0) return [lo]
  const raw = span / 3
  const mag = Math.pow(10, Math.floor(Math.log10(raw)))
  const step = [1, 2, 2.5, 5, 10].map(m => m * mag).find(s => s >= raw) ?? mag * 10
  const out: number[] = []
  for (let t = Math.ceil(lo / step) * step; t <= hi + 1e-9; t += step) out.push(Math.round(t * 1e6) / 1e6)
  return out
}

function timeLabel(t: number, hours: number): string {
  const d = new Date(t)
  return hours <= 48
    ? d.toLocaleTimeString([], { hour: 'numeric' })
    : d.toLocaleDateString([], { month: 'numeric', day: 'numeric' })
}

export function MetricChart({ host, name }: { host: string; name: string }) {
  const [hours, setHours] = useState(168)
  const [hover, setHover] = useState<Point | null>(null)
  const svgRef = useRef<SVGSVGElement>(null)

  // The result carries the request it answers. Deriving "loading" from a key
  // mismatch, rather than setting it at the top of the effect, keeps the effect
  // free of a synchronous setState — and means switching 7d → 30d shows the
  // spinner immediately instead of leaving the old line up for a beat.
  const key = `${host}|${name}|${hours}`
  const [res, setRes] = useState<{ key: string; state: 'ready' | 'error'; data: Series | null } | null>(null)

  useEffect(() => {
    let alive = true
    fetch(`/api/monitor/history?host=${encodeURIComponent(host)}&name=${encodeURIComponent(name)}&hours=${hours}`,
      { cache: 'no-store' })
      .then(r => (r.ok ? r.json() : Promise.reject(new Error(String(r.status)))))
      .then((j: Series) => { if (alive) { setRes({ key, state: 'ready', data: j }); setHover(null) } })
      .catch(() => { if (alive) { setRes({ key, state: 'error', data: null }); setHover(null) } })
    return () => { alive = false }
  }, [key, host, name, hours])

  const state = res?.key === key ? res.state : 'loading'
  const data = res?.key === key ? res.data : null

  const geom = useMemo(() => {
    const pts = data?.points ?? []
    if (pts.length < 2) return null

    // Percentages get a true 0–100 frame: the reading is already a share of a
    // known limit, and rescaling it to its own min/max turns 71%→74% into a
    // cliff. Everything else is padded around what it actually did, because
    // forcing 0 on a TLS countdown or a load average wastes the whole plot.
    const vs = pts.map(p => p.v)
    const isPct = data?.unit === '%'
    let lo = isPct ? 0 : Math.min(...vs)
    let hi = isPct ? 100 : Math.max(...vs)
    if (!isPct) {
      const pad = (hi - lo) * 0.15 || Math.max(Math.abs(hi) * 0.1, 1)
      lo -= pad; hi += pad
      if (Math.min(...vs) >= 0 && lo < 0) lo = 0
    }
    const t0 = pts[0].t, t1 = pts[pts.length - 1].t
    const x = (t: number) => PAD.left + (t1 === t0 ? PLOT.w : ((t - t0) / (t1 - t0)) * PLOT.w)
    const y = (v: number) => PAD.top + PLOT.h - ((v - lo) / (hi - lo || 1)) * PLOT.h

    // Break the line where reporting stopped. Points are evenly spaced buckets,
    // so a jump of more than ~2.5 of them is real silence — and drawing straight
    // through it would invent readings for hours the host was not talking.
    const step = pts.length > 1 ? (t1 - t0) / (pts.length - 1) : 0
    const segs: Point[][] = []
    let cur: Point[] = []
    for (const p of pts) {
      if (cur.length && step > 0 && p.t - cur[cur.length - 1].t > step * 2.5) { segs.push(cur); cur = [] }
      cur.push(p)
    }
    if (cur.length) segs.push(cur)

    const path = (seg: Point[]) => seg.map((p, i) => `${i ? 'L' : 'M'}${x(p.t).toFixed(1)},${y(p.v).toFixed(1)}`).join(' ')
    return { pts, lo, hi, x, y, segs, path, last: pts[pts.length - 1], ticks: axisTicks(lo, hi) }
  }, [data])

  const onMove = (e: React.MouseEvent<SVGSVGElement>) => {
    if (!geom) return
    const r = svgRef.current?.getBoundingClientRect()
    if (!r) return
    const vx = ((e.clientX - r.left) / r.width) * W
    let best = geom.pts[0], bd = Infinity
    for (const p of geom.pts) {
      const d = Math.abs(geom.x(p.t) - vx)
      if (d < bd) { bd = d; best = p }
    }
    setHover(best)
  }

  return (
    <div className="mt-3">
      <div className="mb-2 flex items-center justify-between gap-2">
        <span className="text-[11px] uppercase tracking-wide text-muted-foreground">History</span>
        <div className="flex gap-1" role="group" aria-label="Time range">
          {RANGES.map(r => (
            <button
              key={r.hours}
              onClick={() => setHours(r.hours)}
              aria-pressed={hours === r.hours}
              className={`rounded px-2 py-1 text-[11px] leading-none transition-colors ${
                hours === r.hours
                  ? 'bg-accent text-accent-foreground'
                  : 'text-muted-foreground hover:bg-accent/60'
              }`}
            >
              {r.label}
            </button>
          ))}
        </div>
      </div>

      {state === 'loading' && (
        <div className="flex h-[110px] items-center justify-center text-muted-foreground">
          <Loader2 className="h-4 w-4 animate-spin" />
        </div>
      )}

      {state === 'error' && (
        <p className="py-6 text-center text-xs text-muted-foreground">Could not load history.</p>
      )}

      {/* Distinguish "nothing recorded yet" from "flat line". History only starts
          accruing when this shipped, so for the first hours every chart is empty
          and saying so is kinder than an axis with nothing in it. */}
      {state === 'ready' && !geom && (
        <p className="py-6 text-center text-xs text-muted-foreground">
          {data?.samples ? 'Not enough history yet — one reading so far.' : 'No history recorded for this range yet.'}
        </p>
      )}

      {state === 'ready' && geom && (
        <>
          <svg
            ref={svgRef}
            viewBox={`0 0 ${W} ${H}`}
            className="h-auto w-full touch-none"
            role="img"
            aria-label={`${fmt(geom.last.v, data?.unit ?? null)} now; ${data?.samples ?? 0} readings over the last ${RANGES.find(r => r.hours === hours)?.label}`}
            onMouseMove={onMove}
            onMouseLeave={() => setHover(null)}
          >
            {/* Gridlines: hairline, solid, one step off the surface. */}
            {geom.ticks.map(t => (
              <g key={t}>
                <line
                  x1={PAD.left} x2={PAD.left + PLOT.w} y1={geom.y(t)} y2={geom.y(t)}
                  stroke="currentColor" strokeWidth={1} className="text-border"
                />
                <text
                  x={PAD.left - 6} y={geom.y(t)} dy="0.32em" textAnchor="end"
                  className="fill-muted-foreground text-[9px] tabular-nums"
                >
                  {t}
                </text>
              </g>
            ))}

            <text x={PAD.left} y={H - 6} className="fill-muted-foreground text-[9px]">
              {timeLabel(geom.pts[0].t, hours)}
            </text>
            <text x={PAD.left + PLOT.w} y={H - 6} textAnchor="end" className="fill-muted-foreground text-[9px]">
              {timeLabel(geom.last.t, hours)}
            </text>

            {/* Area wash under each segment, ~10% of the series hue. */}
            {geom.segs.filter(s => s.length > 1).map((seg, i) => (
              <path
                key={`a${i}`}
                d={`${geom.path(seg)} L${geom.x(seg[seg.length - 1].t).toFixed(1)},${(PAD.top + PLOT.h).toFixed(1)} L${geom.x(seg[0].t).toFixed(1)},${(PAD.top + PLOT.h).toFixed(1)} Z`}
                fill="var(--chart-line)"
                opacity={0.1}
              />
            ))}

            {geom.segs.map((seg, i) =>
              seg.length > 1 ? (
                <path key={`l${i}`} d={geom.path(seg)} fill="none" stroke="var(--chart-line)"
                      strokeWidth={2} strokeLinejoin="round" strokeLinecap="round" />
              ) : (
                <circle key={`l${i}`} cx={geom.x(seg[0].t)} cy={geom.y(seg[0].v)} r={2} fill="var(--chart-line)" />
              )
            )}

            {hover && (
              <g>
                <line
                  x1={geom.x(hover.t)} x2={geom.x(hover.t)} y1={PAD.top} y2={PAD.top + PLOT.h}
                  stroke="currentColor" strokeWidth={1} className="text-border"
                />
                <circle cx={geom.x(hover.t)} cy={geom.y(hover.v)} r={4}
                        fill="var(--chart-line)" stroke="var(--card)" strokeWidth={2} />
              </g>
            )}

            {/* End marker + the one direct label worth having: where it stands now. */}
            <circle cx={geom.x(geom.last.t)} cy={geom.y(geom.last.v)} r={4}
                    fill="var(--chart-line)" stroke="var(--card)" strokeWidth={2} />
            <text
              x={Math.min(geom.x(geom.last.t) + 8, W - 2)} y={geom.y(geom.last.v)} dy="0.32em"
              className="fill-foreground text-[10px] font-medium tabular-nums"
            >
              {fmt(geom.last.v, data?.unit ?? null)}
            </text>
          </svg>

          <p className="mt-1 h-4 text-[11px] text-muted-foreground tabular-nums">
            {hover
              ? `${new Date(hover.t).toLocaleString([], { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' })} · ${fmt(hover.v, data?.unit ?? null)}${
                  hover.hi - hover.lo > 0.05 ? ` (${fmt(hover.lo, data?.unit ?? null)}–${fmt(hover.hi, data?.unit ?? null)})` : ''
                }`
              : `${data?.samples ?? 0} readings`}
          </p>
        </>
      )}
    </div>
  )
}
