// calibrate.jsx — appliance calibration (INTERNAL TOOL, not a resident screen).
//
// The goal: teach the app to recognise appliances from the whole-home clamp.
// Someone isolates one appliance, records the window it ran in, and we end up
// with labelled examples to build power signatures from. Later the app guesses
// and residents confirm with one tap.
//
// Reached only via ?calibrate=1 — there is no link to it anywhere in the app,
// so residents never land here.
//
// ── Backend status ───────────────────────────────────────────────────────────
// The endpoints this screen needs do NOT exist yet (all 404 today) — they're
// specified in docs/CALIBRATION_BACKEND_HANDOFF.md:
//   GET    /devices/{id}/readings?from=&to=&channel=   raw samples for a window
//   GET    /household/calibration/runs                 list
//   POST   /household/calibration/runs                 save
//   DELETE /household/calibration/runs/{id}            remove
// Until they return 200, Save stays DISABLED and nothing is written to the
// browser. That's deliberate: calibration data must live in Supabase, not in
// one person's localStorage where switching phone or clearing the browser
// loses it. The screen detects readiness by calling the list endpoint and
// treating 404 as "not built yet"; it switches on by itself once deployed.
//
// What DOES work today: the live readout. While a run is in progress the
// screen watches the clamp so you can see the appliance's draw in real time
// and tell immediately whether it stands out from the background. That reading
// is held in memory for the session only and is never persisted.

(function () {
const { useState, useEffect, useRef } = React;

// Free text, because the backend's appliance_types are far too coarse for this
// (6 categories, no kettle / fridge / microwave). These are just quick fills.
const CAL_SUGGESTIONS = [
  'Kettle', 'Fridge', 'Water heater', 'Washing machine', 'Microwave',
  'Induction hob', 'Rice cooker', 'TV', 'Standing fan', 'Dryer',
];

const CAL_CHANNELS = [
  { channel: 1, label: 'Whole home' },
  { channel: 2, label: 'Circuit 1' },
  { channel: 3, label: 'Circuit 2' },
];


function fmtDuration(ms) {
  const s = Math.max(0, Math.round(ms / 1000));
  const m = Math.floor(s / 60);
  return m > 0 ? `${m}m ${String(s % 60).padStart(2, '0')}s` : `${s}s`;
}
function fmtClock(t) {
  const d = new Date(t);
  return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
}

// A run's samples → the numbers that actually matter for a signature.
function summarise(samples) {
  if (!samples || samples.length === 0) return null;
  const w = samples.map((s) => s.w);
  const mean = w.reduce((a, b) => a + b, 0) / w.length;
  return {
    count: w.length,
    mean: Math.round(mean),
    peak: Math.round(Math.max(...w)),
    min: Math.round(Math.min(...w)),
  };
}

// Simple line chart of a run's samples. Irregular spacing is fine — x is index.
function TraceChart({ c, samples, height = 90, color }) {
  if (!samples || samples.length < 2) return null;
  const w = samples.map((s) => s.w);
  const max = Math.max(...w, 1);
  const min = Math.min(...w, 0);
  const span = Math.max(1, max - min);
  const pts = samples.map((s, i) => {
    const x = (i / (samples.length - 1)) * 100;
    const y = 100 - ((s.w - min) / span) * 100;
    return `${x.toFixed(2)},${y.toFixed(2)}`;
  }).join(' ');
  return (
    <div style={{ position: 'relative' }}>
      <svg viewBox="0 0 100 100" preserveAspectRatio="none" style={{ width: '100%', height, display: 'block' }}>
        <polyline points={pts} fill="none" stroke={color || c.primary} strokeWidth="1.5"
          vectorEffect="non-scaling-stroke" strokeLinejoin="round" strokeLinecap="round" />
      </svg>
      <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 10.5, fontWeight: 600, color: c.textLight, marginTop: 4 }}>
        <span>{Math.round(min)}W</span><span>peak {Math.round(max)}W</span>
      </div>
    </div>
  );
}

function CalBanner({ c, state }) {
  const cfg = {
    checking: { bg: c.surfaceAlt, fg: c.textMuted, text: 'Checking whether saving is switched on…' },
    missing:  { bg: c.warnSoft, fg: '#7C5A1B', text: "Saving isn't available yet — the backend endpoints for calibration haven't shipped. Runs are shown live but can't be stored, so nothing is kept in this browser." },
    ready:    { bg: c.goodSoft, fg: '#2F6147', text: 'Saving is live — runs are stored to your household.' },
  }[state];
  return (
    <div style={{ background: cfg.bg, borderRadius: 14, padding: '11px 13px', fontSize: 12.5, fontWeight: 600, color: cfg.fg, lineHeight: 1.4 }}>
      {cfg.text}
    </div>
  );
}

function CalibrationScreen({ c, onClose }) {
  const clamp = useClamp();                    // same poller the Home screen uses
  const [label, setLabel] = useState('');
  const [channel, setChannel] = useState(1);
  const [notes, setNotes] = useState('');

  // 'checking' | 'missing' | 'ready' — whether the backend can store runs.
  const [backend, setBackend] = useState('checking');
  const [runs, setRuns] = useState([]);
  useEffect(() => {
    let alive = true;
    SaveHorAPI.getCalibrationRuns()
      .then((r) => { if (alive) { setBackend('ready'); setRuns((r && r.runs) || r || []); } })
      .catch((e) => { if (alive) setBackend(e && e.status === 404 ? 'missing' : 'missing'); });
    return () => { alive = false; };
  }, []);

  // A run in progress: started_at + the samples seen since. Memory only.
  const [run, setRun] = useState(null);        // { startedAt, samples: [{ t, w }] }
  const [done, setDone] = useState(null);      // the finished run, awaiting save
  const [tick, setTick] = useState(0);         // re-render the elapsed clock
  useEffect(() => {
    if (!run) return undefined;
    const id = setInterval(() => setTick((n) => n + 1), 1000);
    return () => clearInterval(id);
  }, [run]);

  // Every time the clamp reports a new figure for the chosen channel, record it.
  const view = clamp.channel(channel);
  const watts = view ? view.watts : null;
  const lastAt = clamp.updatedAgo;
  useEffect(() => {
    if (!run || watts == null) return;
    setRun((r) => (r ? { ...r, samples: [...r.samples, { t: Date.now(), w: watts }] } : r));
  }, [watts, lastAt]);   // lastAt changes each poll, so a steady reading still records

  const start = () => { setDone(null); setRun({ startedAt: Date.now(), samples: [] }); };
  const stop = () => {
    if (!run) return;
    setDone({ label: label.trim() || 'Unlabelled', channel, startedAt: run.startedAt, endedAt: Date.now(), samples: run.samples, notes });
    setRun(null);
  };

  const canStart = !!clamp.hasClamp && !run;
  const summary = summarise(done ? done.samples : run ? run.samples : null);
  const elapsed = run ? Date.now() - run.startedAt : done ? done.endedAt - done.startedAt : 0;

  const field = (labelText, node) => (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      <div style={{ fontSize: 11, fontWeight: 700, color: c.textLight, letterSpacing: 0.4, textTransform: 'uppercase' }}>{labelText}</div>
      {node}
    </div>
  );
  const inputStyle = {
    width: '100%', height: 44, padding: '0 12px', borderRadius: 12, fontFamily: 'inherit',
    fontSize: 15, color: c.text, background: c.surface, border: `1.5px solid ${c.border}`,
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 16, padding: '12px 20px 120px' }}>
      {/* header */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
        <button onClick={onClose} aria-label="Back" style={{ width: 36, height: 36, border: 'none', borderRadius: 999, background: c.surfaceAlt, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
          <IcArrowL size={17} color={c.textMuted} />
        </button>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 20, fontWeight: 700, color: c.text, letterSpacing: -0.3 }}>Calibration</div>
          <div style={{ fontSize: 12.5, color: c.textMuted }}>Internal tool · teach the app what each appliance looks like</div>
        </div>
      </div>

      <CalBanner c={c} state={backend} />

      {!clamp.hasClamp ? (
        <div style={{ borderRadius: 14, border: `1.5px dashed ${c.border}`, padding: 16, textAlign: 'center', fontSize: 13, fontWeight: 600, color: c.textMuted }}>
          {clamp.status === 'loading' ? 'Looking for a CT clamp on this household…' : 'This household has no CT clamp, so there is nothing to calibrate against.'}
        </div>
      ) : (
        <>
          {/* live readout */}
          <div style={{ background: c.surfaceAlt, borderRadius: 18, padding: 14, display: 'flex', flexDirection: 'column', gap: 10 }}>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
              <span style={{ fontSize: 11, fontWeight: 800, letterSpacing: 0.5, color: c.textLight }}>
                {run ? 'RECORDING' : 'LIVE'} · {CAL_CHANNELS.find((x) => x.channel === channel).label}
              </span>
              {run && <span style={{ fontSize: 12.5, fontWeight: 700, color: c.primary, fontVariantNumeric: 'tabular-nums' }}>{fmtDuration(elapsed)}</span>}
            </div>
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 5 }}>
              <span style={{ fontSize: 40, fontWeight: 800, letterSpacing: -1.4, lineHeight: 1, color: c.text, fontVariantNumeric: 'tabular-nums' }}>
                {watts != null ? Math.round(watts).toLocaleString() : '—'}
              </span>
              <span style={{ fontSize: 17, fontWeight: 700, color: c.textMuted }}>W</span>
            </div>
            {run && run.samples.length > 1 && <TraceChart c={c} samples={run.samples} height={70} />}
            {run && <div style={{ fontSize: 11.5, color: c.textLight }}>
              {run.samples.length} {run.samples.length === 1 ? 'reading' : 'readings'} so far · one roughly every 15s
            </div>}
          </div>

          {/* what's being calibrated */}
          {field('Appliance', (
            <>
              <input value={label} onChange={(e) => setLabel(e.target.value)} placeholder="e.g. Kettle" style={inputStyle} />
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 2 }}>
                {CAL_SUGGESTIONS.map((s) => (
                  <button key={s} onClick={() => setLabel(s)} style={{
                    padding: '6px 10px', borderRadius: 999, cursor: 'pointer', fontFamily: 'inherit',
                    fontSize: 12, fontWeight: 600, border: `1px solid ${label === s ? c.primary : c.borderSoft}`,
                    background: label === s ? c.primarySoft : c.surface, color: label === s ? c.primaryDk : c.textMuted,
                  }}>{s}</button>
                ))}
              </div>
            </>
          ))}

          {field('Measured on', (
            <div style={{ display: 'flex', background: c.surfaceAlt, padding: 3, borderRadius: 12, gap: 2 }}>
              {CAL_CHANNELS.map((ch) => (
                <button key={ch.channel} onClick={() => setChannel(ch.channel)} disabled={!!run} style={{
                  flex: 1, height: 38, border: 'none', borderRadius: 9, cursor: run ? 'default' : 'pointer', fontFamily: 'inherit',
                  fontSize: 13, fontWeight: 600, background: channel === ch.channel ? c.surface : 'transparent',
                  color: channel === ch.channel ? c.text : c.textMuted, opacity: run && channel !== ch.channel ? 0.5 : 1,
                  boxShadow: channel === ch.channel ? '0 1px 2px rgba(0,0,0,0.06)' : 'none',
                }}>{ch.label}</button>
              ))}
            </div>
          ))}

          {field('Notes', (
            <input value={notes} onChange={(e) => setNotes(e.target.value)} placeholder="e.g. fridge was also running" style={inputStyle} />
          ))}

          {/* start / stop */}
          {run ? (
            <Btn kind="danger" size="lg" full c={c} onClick={stop}>Stop recording</Btn>
          ) : (
            <Btn kind="primary" size="lg" full c={c} onClick={canStart ? start : undefined}
              style={{ opacity: canStart ? 1 : 0.5 }}>Start recording</Btn>
          )}

          {/* the finished run */}
          {done && summary && (
            <div style={{ background: c.surface, border: `1.5px solid ${c.borderSoft}`, borderRadius: 18, padding: 14, display: 'flex', flexDirection: 'column', gap: 12 }}>
              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
                <div>
                  <div style={{ fontSize: 16, fontWeight: 700, color: c.text }}>{done.label}</div>
                  <div style={{ fontSize: 12, color: c.textMuted, marginTop: 1 }}>
                    {fmtClock(done.startedAt)} → {fmtClock(done.endedAt)} · {fmtDuration(done.endedAt - done.startedAt)}
                  </div>
                </div>
                <button onClick={() => setDone(null)} aria-label="Discard" style={{ width: 32, height: 32, border: 'none', borderRadius: 999, background: c.surfaceAlt, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                  <IcX size={15} color={c.textMuted} />
                </button>
              </div>

              <TraceChart c={c} samples={done.samples} />

              <div style={{ display: 'flex', gap: 8 }}>
                {[['Average', `${summary.mean} W`], ['Peak', `${summary.peak} W`], ['Lowest', `${summary.min} W`]].map(([k, v]) => (
                  <div key={k} style={{ flex: 1, background: c.surfaceAlt, borderRadius: 12, padding: '9px 10px' }}>
                    <div style={{ fontSize: 10, fontWeight: 700, color: c.textLight, letterSpacing: 0.4, textTransform: 'uppercase' }}>{k}</div>
                    <div style={{ fontSize: 15, fontWeight: 700, color: c.text, marginTop: 1, fontVariantNumeric: 'tabular-nums' }}>{v}</div>
                  </div>
                ))}
              </div>

              <div style={{ fontSize: 11.5, color: c.textLight, lineHeight: 1.4 }}>
                {summary.count} readings · roughly {SaveHorTariff.costPerHour(summary.mean).toFixed(2)} $/h while running
              </div>

              {backend === 'ready' ? (
                <Btn kind="primary" size="lg" full c={c} onClick={async () => {
                  const saved = await SaveHorAPI.createCalibrationRun({
                    device_id: clamp.device.id, channel: done.channel, label: done.label,
                    started_at: new Date(done.startedAt).toISOString(),
                    ended_at: new Date(done.endedAt).toISOString(),
                    notes: done.notes || null,
                  }).catch(() => null);
                  if (saved) { setRuns((r) => [saved, ...r]); setDone(null); }
                }}>Save this run</Btn>
              ) : (
                <>
                  <Btn kind="primary" size="lg" full c={c} style={{ opacity: 0.45 }}>Save this run</Btn>
                  <div style={{ fontSize: 11.5, color: c.textMuted, textAlign: 'center', lineHeight: 1.4 }}>
                    Saving switches on once the backend is ready. Nothing is stored in this browser,
                    so this run is lost when you leave — note the times if it matters.
                  </div>
                </>
              )}
            </div>
          )}

          {/* saved runs */}
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            <div style={{ fontSize: 15, fontWeight: 700, color: c.text }}>Saved runs</div>
            {backend !== 'ready' ? (
              <div style={{ fontSize: 12.5, color: c.textMuted }}>None yet — saving isn't switched on.</div>
            ) : runs.length === 0 ? (
              <div style={{ fontSize: 12.5, color: c.textMuted }}>No runs recorded yet.</div>
            ) : runs.map((r) => (
              <div key={r.id} style={{ display: 'flex', alignItems: 'center', gap: 10, background: c.surface, border: `1px solid ${c.borderSoft}`, borderRadius: 12, padding: '10px 12px' }}>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 13.5, fontWeight: 700, color: c.text }}>{r.label}</div>
                  <div style={{ fontSize: 11.5, color: c.textMuted }}>
                    {new Date(r.started_at).toLocaleString()} · {CAL_CHANNELS.find((x) => x.channel === r.channel)?.label || `Channel ${r.channel}`}
                  </div>
                </div>
                <button onClick={async () => {
                  await SaveHorAPI.deleteCalibrationRun(r.id).catch(() => {});
                  setRuns((list) => list.filter((x) => x.id !== r.id));
                }} aria-label="Delete run" style={{ width: 30, height: 30, border: 'none', borderRadius: 999, background: c.surfaceAlt, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                  <IcX size={14} color={c.textMuted} />
                </button>
              </div>
            ))}
          </div>
        </>
      )}
    </div>
  );
}

Object.assign(window, { CalibrationScreen });
})();
