// charts.jsx — canvas-drawn instrument-style charts
// Shared aesthetic: faint grid, mono labels, thin lines. X is time (rows carry `ts`),
// so a station that was offline shows a gap instead of a squeezed line.

const { useEffect, useRef, useState } = React;

function setupCanvas(cv) {
  const dpr = window.devicePixelRatio || 1;
  const rect = cv.getBoundingClientRect();
  cv.width = rect.width * dpr;
  cv.height = rect.height * dpr;
  const ctx = cv.getContext('2d');
  ctx.scale(dpr, dpr);
  ctx.clearRect(0, 0, rect.width, rect.height);
  return { ctx, w: rect.width, h: rect.height };
}

// Tiny sparkline (no axes) for sensor cards.
function Sparkline({ data, height = 28, color, dim, valueKey }) {
  const ref = useRef(null);
  useEffect(() => {
    const cv = ref.current;
    if (!cv) return;
    const { ctx, w, h: fullH } = setupCanvas(cv);
    if (!data || data.length < 2) return;

    const vals = data.map(d => valueKey ? d[valueKey] : d).filter(v => v != null && Number.isFinite(v));
    if (vals.length < 2) return;
    const min = Math.min(...vals);
    const max = Math.max(...vals);
    const range = (max - min) || 1;
    const pad = 3;
    const h = fullH - pad * 2;

    ctx.strokeStyle = dim;
    ctx.lineWidth = 0.5;
    ctx.setLineDash([2, 3]);
    ctx.beginPath();
    const avg = vals.reduce((a, b) => a + b, 0) / vals.length;
    const ay = pad + (1 - (avg - min) / range) * h;
    ctx.moveTo(0, ay); ctx.lineTo(w, ay);
    ctx.stroke();
    ctx.setLineDash([]);

    ctx.strokeStyle = color;
    ctx.lineWidth = 1.4;
    ctx.beginPath();
    vals.forEach((v, i) => {
      const x = (i / (vals.length - 1)) * w;
      const y = pad + (1 - (v - min) / range) * h;
      if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
    });
    ctx.stroke();

    const ly = pad + (1 - (vals[vals.length - 1] - min) / range) * h;
    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.arc(w, ly, 1.8, 0, Math.PI * 2);
    ctx.fill();
  }, [data, color, dim, valueKey]);

  return <canvas ref={ref} style={{ width: '100%', height, display: 'block' }} />;
}

// Rain model output over the last 3 h (one probability per reading, for the next 3 h).
function RainTimeline({ history, threshold, dark, nowSec }) {
  const ref = useRef(null);
  useEffect(() => {
    const cv = ref.current;
    if (!cv) return;
    const { ctx, w, h } = setupCanvas(cv);

    const padL = 28, padR = 14, padT = 18, padB = 24;
    const innerW = w - padL - padR;
    const innerH = h - padT - padB;

    const grid = dark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.06)';
    const gridStrong = dark ? 'rgba(255,255,255,0.10)' : 'rgba(0,0,0,0.10)';
    const text = dark ? 'rgba(255,255,255,0.45)' : 'rgba(0,0,0,0.5)';
    const accent = 'oklch(0.78 0.14 75)';
    const cool = 'oklch(0.78 0.10 200)';

    ctx.strokeStyle = grid;
    ctx.lineWidth = 0.5;
    for (let p = 0; p <= 100; p += 10) {
      const y = padT + (1 - p / 100) * innerH;
      ctx.beginPath(); ctx.moveTo(padL, y); ctx.lineTo(w - padR, y); ctx.stroke();
    }
    for (let m = 0; m <= 180; m += 30) {
      const x = padL + (m / 180) * innerW;
      ctx.beginPath(); ctx.moveTo(x, padT); ctx.lineTo(x, h - padB); ctx.stroke();
    }
    ctx.strokeStyle = gridStrong;
    [0, 50, 100].forEach(p => {
      const y = padT + (1 - p / 100) * innerH;
      ctx.beginPath(); ctx.moveTo(padL, y); ctx.lineTo(w - padR, y); ctx.stroke();
    });

    const cutoff = nowSec - 3 * 3600;
    const pts = (history || []).filter(d => d.rain_probability != null && Number.isFinite(d.rain_probability) && d.ts >= cutoff);
    const latestProb = pts.length ? pts[pts.length - 1].rain_probability : 0;
    const stormy = latestProb >= threshold;
    const lineColor = stormy ? accent : cool;

    const thY = padT + (1 - threshold) * innerH;
    ctx.strokeStyle = stormy ? 'oklch(0.78 0.14 75 / 0.7)' : 'oklch(0.78 0.10 200 / 0.5)';
    ctx.lineWidth = 1;
    ctx.setLineDash([3, 3]);
    ctx.beginPath(); ctx.moveTo(padL, thY); ctx.lineTo(w - padR, thY); ctx.stroke();
    ctx.setLineDash([]);
    ctx.fillStyle = lineColor;
    ctx.font = '500 9px JetBrains Mono, monospace';
    ctx.textAlign = 'left';
    ctx.fillText(`TH ${(threshold * 100).toFixed(1)}%`, padL + 4, thY - 4);

    ctx.fillStyle = text;
    ctx.textAlign = 'right';
    [0, 50, 100].forEach(p => ctx.fillText(p + '%', padL - 6, padT + (1 - p / 100) * innerH + 3));
    ctx.textAlign = 'center';
    ['-3h', '-2h30', '-2h', '-1h30', '-1h', '-30m', 'AGORA'].forEach((label, i) => {
      ctx.fillText(label, padL + (i / 6) * innerW, h - 8);
    });

    if (pts.length < 2) return;
    const xOf = (p) => padL + (1 - (nowSec - p.ts) / (180 * 60)) * innerW;
    const yOf = (p) => padT + (1 - Math.max(0, Math.min(1, p.rain_probability))) * innerH;

    ctx.strokeStyle = lineColor;
    ctx.lineWidth = 1.8;
    ctx.beginPath();
    pts.forEach((p, i) => (i === 0 ? ctx.moveTo(xOf(p), yOf(p)) : ctx.lineTo(xOf(p), yOf(p))));
    ctx.stroke();

    const last = pts[pts.length - 1];
    ctx.fillStyle = lineColor;
    ctx.beginPath();
    ctx.arc(xOf(last), yOf(last), 3.5, 0, Math.PI * 2);
    ctx.fill();
  });
  return <canvas ref={ref} style={{ width: '100%', height: 170, display: 'block' }} />;
}

// Time chart with grid, axes, one or more line series (or rain bars), hover crosshair.
//   series: [{ key, color, label }]   bars: sum the first series into buckets (rain)
function InstrumentChart({ data, series, label, unit, hours, dark, decimals = 1, nowSec, bars = false, minZero = false, bounds = null }) {
  const ref = useRef(null);
  const [hoverTs, setHoverTs] = useState(null);
  const padL = 38, padR = 12, padT = 16, padB = 22;
  const span = hours * 3600;
  const cutoff = nowSec - span;
  const main = series[0];

  const slice = (data || []).filter(d => d.ts >= cutoff);
  const valid = (d, key) => d[key] != null && Number.isFinite(d[key]);

  // Bars: bucket sums (rain mm per hour / 3 h / 6 h)
  const bucket = hours <= 24 ? 3600 : hours <= 48 ? 3 * 3600 : 6 * 3600;
  const buckets = [];
  if (bars) {
    const first = Math.floor(cutoff / bucket) * bucket;
    for (let t = first; t < nowSec; t += bucket) buckets.push({ ts: t, v: 0, n: 0 });
    for (const d of slice) {
      if (!valid(d, main.key)) continue;
      const i = Math.floor((d.ts - first) / bucket);
      if (buckets[i]) { buckets[i].v += d[main.key]; buckets[i].n++; }
    }
  }

  useEffect(() => {
    const cv = ref.current;
    if (!cv) return;
    const { ctx, w, h } = setupCanvas(cv);
    const innerW = w - padL - padR;
    const innerH = h - padT - padB;

    let vals = [];
    if (bars) vals = buckets.filter(b => b.n).map(b => b.v);
    else for (const s of series) for (const d of slice) if (valid(d, s.key)) vals.push(d[s.key]);

    const grid = dark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)';
    const gridStrong = dark ? 'rgba(255,255,255,0.10)' : 'rgba(0,0,0,0.10)';
    const text = dark ? 'rgba(255,255,255,0.42)' : 'rgba(0,0,0,0.5)';

    let min = vals.length ? Math.min(...vals) : 0;
    let max = vals.length ? Math.max(...vals) : 1;
    if (bars || minZero) min = 0;
    if (bars) max = Math.max(max, 1);
    const pad = bars ? 0 : ((max - min) * 0.12 || 1);
    let yMin = minZero ? 0 : min - pad;
    let yMax = max + (bars ? max * 0.1 : pad);
    if (bounds) {
      yMin = Math.max(bounds[0], yMin);
      yMax = Math.min(bounds[1], yMax);
    }
    const yRange = (yMax - yMin) || 1;
    const xOf = (ts) => padL + (1 - (nowSec - ts) / span) * innerW;
    const yOf = (v) => padT + (1 - (v - yMin) / yRange) * innerH;

    ctx.strokeStyle = grid;
    ctx.lineWidth = 0.5;
    for (let i = 0; i <= 5; i++) {
      const y = padT + (i / 5) * innerH;
      ctx.beginPath(); ctx.moveTo(padL, y); ctx.lineTo(w - padR, y); ctx.stroke();
    }
    ctx.strokeStyle = gridStrong;
    ctx.strokeRect(padL, padT, innerW, innerH);

    ctx.fillStyle = text;
    ctx.font = '500 9px JetBrains Mono, monospace';
    ctx.textAlign = 'right';
    for (let i = 0; i <= 4; i++) {
      const v = yMax - (i / 4) * yRange;
      ctx.fillText(v.toFixed(decimals), padL - 6, padT + (i / 4) * innerH + 3);
    }
    ctx.textAlign = 'center';
    const xTicks = 6;
    for (let i = 0; i <= xTicks; i++) {
      const x = padL + (i / xTicks) * innerW;
      const hAgo = Math.round(hours - (i / xTicks) * hours);
      const lbl = hAgo === 0 ? 'agora' : (hours > 48 ? `-${Math.round(hAgo / 24)}d` : `-${hAgo}h`);
      ctx.fillText(lbl, x, h - 6);
      ctx.strokeStyle = grid;
      ctx.beginPath(); ctx.moveTo(x, padT); ctx.lineTo(x, padT + innerH); ctx.stroke();
    }

    if (bars) {
      const bw = Math.max(1, (bucket / span) * innerW - 1.5);
      ctx.fillStyle = main.color;
      for (const b of buckets) {
        if (!b.n || b.v <= 0) continue;
        const x = xOf(b.ts);
        const y = yOf(b.v);
        ctx.fillRect(x + 0.75, y, bw, padT + innerH - y);
      }
    } else {
      // Break the line where the station was silent for more than 3 readings
      const gap = 3 * Math.max(300, medianStep(slice));
      series.forEach((s, si) => {
        const pts = slice.filter(d => valid(d, s.key));
        if (pts.length < 2) return;
        if (si === 0) {
          ctx.fillStyle = s.color.replace(')', ' / 0.13)');
          let open = false;
          pts.forEach((d, i) => {
            const brk = i > 0 && d.ts - pts[i - 1].ts > gap;
            if (!open || brk) {
              if (open) { ctx.lineTo(xOf(pts[i - 1].ts), padT + innerH); ctx.closePath(); ctx.fill(); }
              ctx.beginPath(); ctx.moveTo(xOf(d.ts), padT + innerH); open = true;
            }
            ctx.lineTo(xOf(d.ts), yOf(d[s.key]));
          });
          ctx.lineTo(xOf(pts[pts.length - 1].ts), padT + innerH); ctx.closePath(); ctx.fill();
        }
        ctx.strokeStyle = s.color;
        ctx.lineWidth = si === 0 ? 1.4 : 1;
        if (si > 0) ctx.setLineDash([3, 2]);
        ctx.beginPath();
        pts.forEach((d, i) => {
          const brk = i > 0 && d.ts - pts[i - 1].ts > gap;
          if (i === 0 || brk) ctx.moveTo(xOf(d.ts), yOf(d[s.key])); else ctx.lineTo(xOf(d.ts), yOf(d[s.key]));
        });
        ctx.stroke();
        ctx.setLineDash([]);
      });
    }

    if (hoverTs != null) {
      const x = xOf(hoverTs);
      ctx.strokeStyle = gridStrong;
      ctx.lineWidth = 0.5;
      ctx.setLineDash([2, 2]);
      ctx.beginPath(); ctx.moveTo(x, padT); ctx.lineTo(x, padT + innerH); ctx.stroke();
      ctx.setLineDash([]);
    }
  });

  // Stats (main series)
  let statVals, lastVal = null, hoverVal = null, hoverLabel = null;
  if (bars) {
    statVals = buckets.filter(b => b.n).map(b => b.v);
    const total = statVals.reduce((a, b) => a + b, 0);
    lastVal = statVals.length ? total : null;
    if (hoverTs != null) {
      const b = buckets.find(b => b.ts <= hoverTs && hoverTs < b.ts + bucket);
      if (b && b.n) hoverVal = b.v;
    }
  } else {
    const pts = slice.filter(d => valid(d, main.key));
    statVals = pts.map(d => d[main.key]);
    lastVal = pts.length ? pts[pts.length - 1][main.key] : null;
    if (hoverTs != null && pts.length) {
      let best = pts[0];
      for (const d of pts) if (Math.abs(d.ts - hoverTs) < Math.abs(best.ts - hoverTs)) best = d;
      hoverVal = best[main.key];
      hoverLabel = new Date(best.ts * 1000).toLocaleString('pt-BR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
    }
  }
  const minVal = statVals.length ? Math.min(...statVals) : null;
  const maxVal = statVals.length ? Math.max(...statVals) : null;
  const fmt = (v) => (v != null ? v.toFixed(decimals) : '—');

  const onMove = (e) => {
    const cv = ref.current;
    if (!cv) return;
    const rect = cv.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const innerW = rect.width - padL - padR;
    if (x < padL || x > rect.width - padR) { setHoverTs(null); return; }
    setHoverTs(nowSec - (1 - (x - padL) / innerW) * span);
  };

  return (
    <div className="instr-chart">
      <div className="instr-chart-head">
        <div className="instr-chart-label">
          {label}
          {series.length > 1 && <span className="instr-legend">
            {series.map(s => <span key={s.key}><i style={{ background: s.color }} />{s.label}</span>)}
          </span>}
        </div>
        <div className="instr-chart-stats">
          {bars ? (
            <>
              <span><em>máx</em>{fmt(maxVal)}</span>
              <span className="lv"><em>{hoverTs != null ? 'barra' : 'total'}</em>{hoverTs != null ? fmt(hoverVal) : fmt(lastVal)}<u>{unit}</u></span>
            </>
          ) : (
            <>
              <span><em>mín</em>{fmt(minVal)}</span>
              <span><em>máx</em>{fmt(maxVal)}</span>
              <span className="lv"><em>{hoverLabel || 'agora'}</em>{hoverTs != null ? fmt(hoverVal) : fmt(lastVal)}<u>{unit}</u></span>
            </>
          )}
        </div>
      </div>
      <canvas
        ref={ref}
        style={{ width: '100%', height: 130, display: 'block', cursor: 'crosshair' }}
        onMouseMove={onMove}
        onMouseLeave={() => setHoverTs(null)}
        onTouchMove={(e) => { const t = e.touches[0]; if (t) onMove(t); }}
        onTouchEnd={() => setHoverTs(null)}
      />
    </div>
  );
}

function medianStep(rows) {
  if (rows.length < 3) return 300;
  const steps = [];
  for (let i = Math.max(1, rows.length - 50); i < rows.length; i++) steps.push(rows[i].ts - rows[i - 1].ts);
  steps.sort((a, b) => a - b);
  return steps[Math.floor(steps.length / 2)] || 300;
}

Object.assign(window, { Sparkline, RainTimeline, InstrumentChart });
