/* global React, Icon, fmtPhone, relTime, api, useApi, toast */
/*
 * DebugView — internal-only diagnostics page for the developer.
 *
 * Lives next to the regular hub views but is intentionally NOT wired into
 * the mobile bottom-nav (only desktop sidebar) — Bill should never see this
 * tab on his phone. Anything Bill might want goes in the Today/Calls views.
 *
 * Sections:
 *   1. System status — process up/down, VRAM, audio pool size, disk usage.
 *   2. DB row counts — quick "is data piling up" snapshot.
 *   3. Recent timings — last 50 turn_timings rows in a sortable table.
 *   4. Agent log tail — last 200 lines of /tmp/agent_run.log.
 *   5. Clear customer data — destructive button (with double-confirm).
 *
 * No editing of customer data here on purpose. Edits live in the regular
 * views; this is purely read-only + the wipe button.
 */

const DebugView = () => {
  // ── Live updates ────────────────────────────────────────────────
  // Three layers of "liveness", picked per-section by what makes sense:
  //   1. System + DB counts: poll every 3s (cheap reads, no SSE event
  //      that fires for "process state changed"). 3s is short enough
  //      to feel live, slow enough not to flood ps/curl.
  //   2. Timings: refetch on /api/live SSE events (`turn_timings`,
  //      `call_started`, `call_ended`) so new turn rows appear within
  //      ~100 ms of the agent emitting them. Plus a 5s safety poll
  //      in case SSE drops mid-call.
  //   3. Log tail: poll every 2s. Auto-follow the bottom UNLESS the
  //      user has scrolled up to read history — then hold scroll
  //      position so refreshes don't yank them back to the tail.
  const [tick, setTick] = React.useState(0);              // for system+counts
  const [timingsTick, setTimingsTick] = React.useState(0); // for timings refetch
  const [logTick, setLogTick] = React.useState(0);        // for log refetch
  const [sseConnected, setSseConnected] = React.useState(false);

  // System/counts heartbeat — 3s.
  React.useEffect(() => {
    const iv = setInterval(() => setTick(t => t + 1), 3000);
    return () => clearInterval(iv);
  }, []);
  // Timings safety poll — 5s. SSE will normally beat this, but the poll
  // is the floor in case the bridge restarts and SSE silently drops.
  React.useEffect(() => {
    const iv = setInterval(() => setTimingsTick(t => t + 1), 5000);
    return () => clearInterval(iv);
  }, []);
  // Log heartbeat — 2s. Tightest because the log is what you stare at
  // during a live call to see what just happened.
  React.useEffect(() => {
    const iv = setInterval(() => setLogTick(t => t + 1), 2000);
    return () => clearInterval(iv);
  }, []);

  // SSE — refetch timings the instant the bridge broadcasts a turn or
  // call event. App.jsx already maintains its own /api/live subscription
  // for the live-call indicator; this is a SECOND subscription, which is
  // fine — EventSource per tab is cheap and the bridge fans out to all.
  React.useEffect(() => {
    let es = null;
    let reconnectTimer = null;
    const open = () => {
      es = new EventSource('/api/live');
      es.onopen = () => setSseConnected(true);
      es.onmessage = (ev) => {
        let m;
        try { m = JSON.parse(ev.data); } catch { return; }
        // Any event that could change debug-relevant state → bump.
        if (m.type === 'turn_timings' || m.type === 'turn'
            || m.type === 'call_started' || m.type === 'call_ended'
            || m.type === 'intake_update') {
          setTimingsTick(t => t + 1);
          setTick(t => t + 1);  // also nudge db counts (calls/turns rows changed)
        }
      };
      es.onerror = () => {
        setSseConnected(false);
        if (es) { es.close(); es = null; }
        reconnectTimer = setTimeout(open, 3000);
      };
    };
    open();
    return () => {
      if (es) es.close();
      if (reconnectTimer) clearTimeout(reconnectTimer);
    };
  }, []);

  const { data: sys, loading: loadingSys, error: sysErr } =
    useApi(() => api.debugSystem(), [tick]);
  const { data: counts, loading: loadingCounts } =
    useApi(() => api.debugDbCounts(), [tick]);
  const { data: timings, loading: loadingTimings, refetch: refetchTimings } =
    useApi(() => api.debugTimings(50), [timingsTick]);

  // ── Log tail with smart auto-follow ─────────────────────────────
  // If the user is near the bottom of the log pane, follow the tail on
  // each refresh (jump to bottom). If they've scrolled up to read older
  // lines, hold their scroll position — don't yank them back. "Near the
  // bottom" = within 40 px (covers any slight overshoot from inertial
  // scroll).
  const [logText, setLogText] = React.useState('');
  const [logLines, setLogLines] = React.useState(200);
  const [logBusy, setLogBusy] = React.useState(false);
  const [logPaused, setLogPaused] = React.useState(false);
  const logPreRef = React.useRef(null);

  const loadLog = React.useCallback(async () => {
    setLogBusy(true);
    const pre = logPreRef.current;
    // Capture pre-refresh scroll state.
    let wasNearBottom = true;
    let prevScroll = 0;
    if (pre) {
      prevScroll = pre.scrollTop;
      wasNearBottom = (pre.scrollHeight - pre.clientHeight - pre.scrollTop) < 40;
    }
    try { setLogText(await api.debugLogTail(logLines)); }
    catch (e) { setLogText(`[error: ${e.message}]`); }
    finally {
      setLogBusy(false);
      // After React commits the new text, restore scroll. setTimeout(0)
      // pushes this past the next paint so scrollHeight reflects new content.
      setTimeout(() => {
        const el = logPreRef.current;
        if (!el) return;
        if (wasNearBottom) el.scrollTop = el.scrollHeight;
        else el.scrollTop = prevScroll;
      }, 0);
    }
  }, [logLines]);

  // Initial load + react to lines selector + heartbeat tick (when not paused).
  React.useEffect(() => {
    if (logPaused) return;
    loadLog();
  }, [loadLog, logTick, logPaused]);

  const [clearStage, setClearStage] = React.useState('idle'); // idle|confirm|busy
  const [clearResult, setClearResult] = React.useState(null);
  const doClear = async () => {
    setClearStage('busy');
    try {
      const r = await api.debugClearCustomerData();
      setClearResult(r);
      toast('Customer data cleared.');
      refetchTimings();
    } catch (e) {
      toast('Clear failed: ' + e.message);
    } finally {
      setClearStage('idle');
    }
  };

  const fmtBytes = (n) => {
    if (n == null) return '—';
    if (n < 1024) return n + ' B';
    if (n < 1024 * 1024) return (n / 1024).toFixed(1) + ' KiB';
    if (n < 1024 * 1024 * 1024) return (n / 1024 / 1024).toFixed(1) + ' MiB';
    return (n / 1024 / 1024 / 1024).toFixed(2) + ' GiB';
  };
  const fmtMs = (n) => (n == null ? '—' : Math.round(n) + ' ms');

  return (
    <div className="debug-view">
      <div className="page-head">
        <div>
          <h1>Debug</h1>
          <div className="subtitle">Internal diagnostics — process status, latency timings, DB state, and the reset button. Not visible to Bill.</div>
        </div>
        {/* Live status pill — green when we're subscribed to /api/live and
         * heartbeats are flowing, grey when SSE dropped (we still have
         * the safety polls, just no event-driven updates). */}
        <div className="dbg-live-pill" title={sseConnected ? "Receiving live events from twilio_bridge SSE" : "SSE disconnected — falling back to polling only"}>
          <span className={`dbg-dot ${sseConnected ? 'up' : 'down'}`}></span>
          {sseConnected ? 'Live' : 'Polling only'}
        </div>
      </div>

      {/* ─── System status ─── */}
      <section className="dbg-section">
        <div className="dbg-section-head">
          <h2>System
            <span className="dbg-live-tag">live · 3s</span>
          </h2>
          <span className="dbg-meta">
            {loadingSys ? '…' : (sysErr ? <span style={{color:'var(--clay)'}}>error: {sysErr}</span> : `updated ${relTime(sys?.generated_at)}`)}
          </span>
        </div>
        <div className="dbg-grid">
          {sys && Object.entries(sys.processes || {}).map(([name, p]) => (
            <div key={name} className={`dbg-card ${p.running ? 'ok' : 'down'}`}>
              <div className="dbg-card-label">{name}</div>
              <div className="dbg-card-value">
                <span className={`dbg-dot ${p.running ? 'up' : 'down'}`}></span>
                {p.running ? `pid ${p.pid}` : 'stopped'}
              </div>
              {p.cmd && <div className="dbg-card-sub" title={p.cmd}>{p.cmd}</div>}
            </div>
          ))}
        </div>
        <div className="dbg-grid">
          <div className="dbg-card">
            <div className="dbg-card-label">VRAM</div>
            <div className="dbg-card-value">
              {sys?.vram
                ? `${(sys.vram.used_mib/1024).toFixed(1)} / ${(sys.vram.total_mib/1024).toFixed(1)} GiB (${sys.vram.pct}%)`
                : '—'}
            </div>
          </div>
          <div className="dbg-card">
            <div className="dbg-card-label">Audio pool size</div>
            <div className="dbg-card-value">{sys?.audio_pool_size >= 0 ? sys.audio_pool_size + ' entries' : '—'}</div>
          </div>
          <div className="dbg-card">
            <div className="dbg-card-label">DB</div>
            <div className="dbg-card-value">{fmtBytes(sys?.db_bytes)}</div>
            <div className="dbg-card-sub">{sys?.db_path}</div>
          </div>
          <div className="dbg-card">
            <div className="dbg-card-label">Recordings</div>
            <div className="dbg-card-value">{fmtBytes(sys?.recordings_bytes)}</div>
          </div>
        </div>
      </section>

      {/* ─── DB row counts ─── */}
      <section className="dbg-section">
        <div className="dbg-section-head">
          <h2>Database tables
            <span className="dbg-live-tag">{sseConnected ? 'live · SSE' : 'live · 3s'}</span>
          </h2>
          <span className="dbg-meta">{loadingCounts ? '…' : 'updated just now'}</span>
        </div>
        <div className="dbg-grid">
          {counts && Object.entries(counts).map(([t, n]) => (
            <div key={t} className="dbg-card">
              <div className="dbg-card-label">{t}</div>
              <div className="dbg-card-value">{n == null ? '—' : n}</div>
            </div>
          ))}
        </div>
      </section>

      {/* ─── Recent timings ─── *
       * Updates instantly via SSE (`turn_timings` events from the bridge)
       * during a live call, plus a 5s safety poll. The Refresh button is
       * a manual override for the rare case both fail. */}
      <section className="dbg-section">
        <div className="dbg-section-head">
          <h2>Recent turn timings
            <span className="dbg-live-tag">{sseConnected ? 'live · SSE' : 'live · polling'}</span>
          </h2>
          <span className="dbg-meta">
            <button className="dbg-btn" onClick={refetchTimings} disabled={loadingTimings}>
              {loadingTimings ? '…' : 'Refresh'}
            </button>
          </span>
        </div>
        <div className="dbg-table-wrap">
          <table className="dbg-table">
            <thead>
              <tr>
                <th>When</th>
                <th>Caller</th>
                <th>Turn</th>
                <th title="Perceived turn gap — caller-felt silence">Gap</th>
                <th title="VAD + STT processing">VAD+STT</th>
                <th title="LLM time-to-first-token">LLM TTFT</th>
                <th title="LLM total generation">LLM gen</th>
                <th title="TTS time-to-first-audio">TTS TTFA</th>
                <th title="Preemptive generation">Preempt</th>
                <th>User said</th>
                <th>Agent said</th>
              </tr>
            </thead>
            <tbody>
              {timings && timings.length === 0 && (
                <tr><td colSpan={11} style={{textAlign:'center', padding:'18px', color:'var(--ink-3)'}}>No timings yet.</td></tr>
              )}
              {timings && timings.map(r => (
                <tr key={r.id}>
                  <td title={r.created_at}>{relTime(r.created_at)}</td>
                  <td title={r.call_id}>
                    {r.caller_name || (r.caller_phone ? fmtPhone(r.caller_phone) : '—')}
                  </td>
                  <td>{r.turn_id}</td>
                  <td className={r.perceived_turn_gap_ms > 1500 ? 'dbg-bad' : (r.perceived_turn_gap_ms > 800 ? 'dbg-warn' : '')}>
                    {fmtMs(r.perceived_turn_gap_ms)}
                  </td>
                  <td>{fmtMs(r.vad_plus_stt_ms)}</td>
                  <td>{fmtMs(r.llm_first_token_ms)}</td>
                  <td>{fmtMs(r.llm_gen_total_ms)}</td>
                  <td>{fmtMs(r.tts_first_audio_ms)}</td>
                  <td>{r.preemptive_generation ? '✓' : ''}</td>
                  <td className="dbg-text" title={r.user_text}>{(r.user_text || '').slice(0, 60)}</td>
                  <td className="dbg-text" title={r.agent_text}>{(r.agent_text || '').slice(0, 60)}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </section>

      {/* ─── Agent log tail ───
       * Auto-refreshes every 2s. Auto-follows the bottom (tail -f style)
       * UNLESS the user has scrolled up to read history — in that case,
       * scroll position is preserved across refreshes so the line they're
       * looking at doesn't jump. The Pause button stops refreshes
       * entirely if you need a stable snapshot to copy text out of. */}
      <section className="dbg-section">
        <div className="dbg-section-head">
          <h2>Agent log tail
            <span className="dbg-live-tag">{logPaused ? 'paused' : 'live · 2s'}</span>
          </h2>
          <span className="dbg-meta">
            <select className="dbg-select" value={logLines} onChange={e => setLogLines(Number(e.target.value))}>
              <option value={50}>50</option>
              <option value={200}>200</option>
              <option value={500}>500</option>
              <option value={2000}>2000</option>
            </select>
            <button
              className="dbg-btn"
              onClick={() => setLogPaused(p => !p)}
              title={logPaused ? "Resume auto-refresh" : "Pause auto-refresh (useful when copying text)"}
            >
              {logPaused ? 'Resume' : 'Pause'}
            </button>
            <button className="dbg-btn" onClick={loadLog} disabled={logBusy}>{logBusy ? '…' : 'Refresh'}</button>
          </span>
        </div>
        <pre className="dbg-log" ref={logPreRef}>{logText || '[empty]'}</pre>
      </section>

      {/* ─── Clear customer data ─── */}
      <section className="dbg-section dbg-danger">
        <div className="dbg-section-head">
          <h2>Reset customer data</h2>
        </div>
        <p style={{margin: '0 0 10px', color: 'var(--ink-2)', fontSize: 13.5}}>
          Wipes <strong>customers, appointments, calls, turns, messages, documents, vehicles</strong> and removes all <strong>recording WAVs</strong>. Schema is preserved. <strong>turn_timings stays</strong> (it's operational, not PII). Google Calendar events are <strong>not</strong> touched — clean those up manually.
        </p>
        {clearStage === 'idle' && (
          <button className="dbg-btn dbg-btn-danger" onClick={() => setClearStage('confirm')}>
            Clear all customer data
          </button>
        )}
        {clearStage === 'confirm' && (
          <div className="dbg-confirm">
            <span>Are you sure? This cannot be undone.</span>
            <button className="dbg-btn dbg-btn-danger" onClick={doClear}>Yes, clear everything</button>
            <button className="dbg-btn" onClick={() => setClearStage('idle')}>Cancel</button>
          </div>
        )}
        {clearStage === 'busy' && <div className="dbg-confirm">Clearing…</div>}
        {clearResult && (
          <div className="dbg-result">
            <div style={{fontWeight: 600, marginBottom: 4}}>Last clear:</div>
            {Object.entries(clearResult.before || {}).map(([t, n]) => {
              const after = clearResult.after?.[t];
              return (
                <div key={t} className="dbg-result-row">
                  <span>{t}</span>
                  <span>{n} → {after}</span>
                </div>
              );
            })}
            <div className="dbg-result-row">
              <span>recordings</span>
              <span>{clearResult.recordings_cleared ? 'wiped' : 'failed'}</span>
            </div>
          </div>
        )}
      </section>
    </div>
  );
};

window.DebugView = DebugView;
