'use client';

import { useEffect, useRef, useState, type CSSProperties } from 'react';
import { liveTick } from '@/lib/liveTick';
// Shared freshness tier — ONE source for the desktop HeartbeatBar and the
// mobile app-bar clock (Design's mobile: clock rests, STALE reacts).
import { freshnessTier } from '@/lib/freshness';

/** Subscribe to the single living-layer tick (§8). Starts the shared interval
 *  on first mount; many components can call this — one interval, fan-out. */
export function useLive() {
  const [s, setS] = useState(liveTick.state);
  useEffect(() => {
    liveTick.start();
    const unsub = liveTick.subscribe(setS);
    return () => {
      unsub();
    };
  }, []);
  return s;
}

/** A number that pops (count-tick) when its value changes. Data, not chrome —
 *  the pop is a one-shot transform, killed under reduced-motion by the CSS. */
export function LiveNumber({ value, className = '', style = {} }: { value: number | string; className?: string; style?: CSSProperties }) {
  const [pop, setPop] = useState(false);
  const prev = useRef(value);
  useEffect(() => {
    if (prev.current !== value) {
      prev.current = value;
      setPop(true);
      const t = setTimeout(() => setPop(false), 500);
      return () => clearTimeout(t);
    }
  }, [value]);
  return (
    <span className={`mono ${pop ? 'count-tick' : ''} ${className}`} style={{ display: 'inline-block', ...style }}>
      {value}
    </span>
  );
}

const EKG = 'M0 12 H10 L13 4 L16 20 L19 12 H30 L33 9 L36 15 L39 12 H50';

interface SnapshotSummary {
  graded: number;
  /** Grade-LOCK time — advances only on a full snapshot (5×/day). Intentionally
   *  stable (grades never change in-game); NOT the freshness signal. */
  updated_at: string | null;
  /** Freshness heartbeat — grade lock OR intraday line refresh (~every 20 min).
   *  The SYNC badge keys off THIS, not updated_at. */
  refreshed_at: string | null;
  /** The pipeline's expected cadence (s) — SYNC thresholds key off this. */
  expected_interval_s?: number;
}

const SUMMARY_POLL_MS = 60_000;
// Session 58 (Task 5) — SYNC thresholds are relative to the pipeline's
// EXPECTED interval (SNAPSHOT_EXPECTED_INTERVAL server-side), not a flat 5
// minutes: normal < 1.5x · amber ≥ 1.5x · STALE red ≥ 3x. When Phase 2.5's
// intraday refresh ships, the server drops the interval and this goes
// genuinely live with zero changes here.
const DEFAULT_EXPECTED_S = 18_000; // 5h max cron gap

/** Elapsed-since-snapshot label: MM:SS under an hour, then Xh Ym. */
function syncLabel(elapsedMs: number): string {
  const s = Math.max(0, Math.floor(elapsedMs / 1000));
  if (s < 3600) return `${String(Math.floor(s / 60)).padStart(2, '0')}:${String(s % 60).padStart(2, '0')}`;
  return `${Math.floor(s / 3600)}h ${Math.floor((s % 3600) / 60)}m`;
}

/** The system heartbeat (§8): scrolling EKG + SIGNAL LIVE + graded count +
 *  sync clock. Sits under the ticker.
 *
 *  Session 57 (Phase 0) — every number here is REAL now. The graded count is
 *  the snapshot's actual prop count from /api/snapshot/summary (small and true
 *  beats big and fake), the fabricated brain-% stat is gone entirely, and SYNC
 *  counts up from the last real pipeline run, tiered against the expected
 *  cadence (Session 58). No summary data → "SYNC —" and no invented count. */
export function HeartbeatBar() {
  const live = useLive(); // 1s pulse — re-renders the elapsed clock
  const [summary, setSummary] = useState<SnapshotSummary | null>(null);
  const [mounted, setMounted] = useState(false);
  useEffect(() => { setMounted(true); }, []);

  useEffect(() => {
    let active = true;
    const load = async () => {
      try {
        const r = await fetch('/api/snapshot/summary', { cache: 'no-store' });
        if (!r.ok) return;
        const data = await r.json();
        if (active && typeof data?.graded === 'number') {
          setSummary({
            graded: data.graded,
            updated_at: data.updated_at ?? null,
            refreshed_at: data.refreshed_at ?? data.updated_at ?? null,
            expected_interval_s: Number(data.expected_interval_s) > 0 ? Number(data.expected_interval_s) : undefined,
          });
        }
      } catch {
        /* keep last known real values — never invent */
      }
    };
    load();
    const id = setInterval(load, SUMMARY_POLL_MS);
    return () => { active = false; clearInterval(id); };
  }, []);

  void live.tick; // subscription drives the 1s clock re-render
  // Freshness is measured from the intraday heartbeat (refreshed_at), NOT the
  // grade-lock time (updated_at) — the shared tier util (same thresholds as the
  // mobile app-bar clock). Measuring the 20-min cadence against the 5×/day lock
  // field is what made a live pipeline read "STALE 8h".
  const freshAt = summary?.refreshed_at ?? summary?.updated_at ?? null;
  const syncedAt = freshAt ? Date.parse(freshAt) : NaN;
  const { tier, elapsedMs } = freshnessTier(syncedAt, summary?.expected_interval_s ?? 0, Date.now());
  const elapsed = elapsedMs;
  const syncColor = tier === 'stale' ? 'var(--miss)' : tier === 'amber' ? 'var(--amber)' : 'var(--text-2)';
  // Design's mobile app bar (M1b): the RESTING state is a wall clock (the
  // stillness); STALE/amber is the REACTION (the punctuation), from the SAME
  // real tier above. `mounted` gates the wall clock so SSR ≠ client-time
  // hydration mismatch. The .hb-mobile-clock / .hb-desktop CSS toggle picks
  // which renders per width (globals.css).
  const wallClock = mounted ? new Date().toLocaleTimeString('en-US', { hour12: false }) : '--:--:--';
  const mobileFresh = tier === 'stale'
    ? { label: `STALE ${elapsed !== null ? syncLabel(elapsed) : ''}`.trim(), color: 'var(--miss)', weight: 700 }
    : tier === 'amber'
      ? { label: `SYNC ${elapsed !== null ? syncLabel(elapsed) : '—'}`, color: 'var(--amber)', weight: 700 }
      : { label: wallClock, color: 'var(--text-2)', weight: 400 };
  return (
    <div
      className="scanlines heartbeat-bar"
      style={{
        display: 'flex',
        alignItems: 'center',
        gap: 14,
        height: 30,
        padding: '0 16px',
        background: 'var(--bg-0)',
        borderBottom: '1px solid var(--border)',
        overflow: 'hidden',
        fontSize: 10.5,
      }}
    >
      <span className="hb-desktop" style={{ display: 'inline-flex', alignItems: 'center', gap: 6, flexShrink: 0 }}>
        <span className="live-dot" style={{ background: 'var(--g-a)' }} />
        <span className="mono" style={{ color: 'var(--g-a)', fontWeight: 700, letterSpacing: '0.12em' }}>SIGNAL LIVE</span>
      </span>

      {/* EKG strip — decorative proof-of-life. Hidden <768px (mobile header
          collapses to ONE status line; the signal pulse + ticker are the single
          animated element there — DESIGN-SPEC §4 "one animated element"). */}
      <div className="hb-ekg" style={{ flex: 1, overflow: 'hidden', height: 22, maskImage: 'linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent)' }}>
        <div className="ekg-track" style={{ height: 22 }}>
          {[0, 1].map((k) => (
            <svg key={k} width="200" height="22" viewBox="0 0 200 22" style={{ display: 'block' }} aria-hidden>
              {[0, 50, 100, 150].map((x) => (
                <path key={x} d={EKG} transform={`translate(${x},0)`} fill="none" stroke="var(--g-a)" strokeWidth="1.2" opacity="0.55" />
              ))}
            </svg>
          ))}
        </div>
      </div>

      {summary !== null && (
        <span className="mono hb-desktop" style={{ color: 'var(--text-1)', flexShrink: 0 }}>
          <LiveNumber value={summary.graded} style={{ color: 'var(--text-0)', fontWeight: 700 }} /> graded
        </span>
      )}
      <span className="mono hb-desktop" style={{ color: syncColor, fontWeight: tier === 'stale' ? 700 : 400, flexShrink: 0 }}>
        {tier === 'stale' ? 'STALE' : 'SYNC'} {elapsed !== null ? syncLabel(elapsed) : '—'}
      </span>

      {/* Mobile app-bar clock (M1b, Hybrid) — Design's mobile RESTS as a wall
          clock (the stillness) and only REACTS to amber/STALE (the punctuation)
          off the SAME real freshness tier. Never silently stale on mobile.
          Toggled with .hb-desktop via width (globals.css). */}
      <span
        className="mono hb-mobile-clock"
        style={{ display: 'none', alignItems: 'center', gap: 6, marginLeft: 'auto', color: mobileFresh.color, fontWeight: mobileFresh.weight, flexShrink: 0, letterSpacing: '0.06em' }}
      >
        {/* Resting dot is STATIC (the stillness) — the ticking clock is the
            proof-of-life; the dot only changes COLOR when the tier reacts. */}
        <span style={{ width: 6, height: 6, borderRadius: '50%', background: mobileFresh.color, flexShrink: 0 }} />
        {mobileFresh.label}
      </span>
    </div>
  );
}

/**
 * MobileSyncClock (P0-4) — the mobile APP-BAR clock. Rendered in the Nav's right
 * cluster and shown ONLY <768px (where the ticker + the whole heartbeat bar are
 * hidden), so the mobile header collapses to ONE line: logo + clock + sync dot.
 * Wall clock rests (the stillness); amber → red STALE reacts off the shared
 * freshness tier (refreshed_at vs expected_interval_s). Never silently stale.
 */
export function MobileSyncClock({ className = '' }: { className?: string }) {
  const live = useLive();
  const [summary, setSummary] = useState<SnapshotSummary | null>(null);
  const [mounted, setMounted] = useState(false);
  useEffect(() => { setMounted(true); }, []);
  useEffect(() => {
    let active = true;
    const load = async () => {
      try {
        const r = await fetch('/api/snapshot/summary', { cache: 'no-store' });
        if (!r.ok) return;
        const d = await r.json();
        if (active && typeof d?.graded === 'number') {
          setSummary({
            graded: d.graded,
            updated_at: d.updated_at ?? null,
            refreshed_at: d.refreshed_at ?? d.updated_at ?? null,
            expected_interval_s: Number(d.expected_interval_s) > 0 ? Number(d.expected_interval_s) : undefined,
          });
        }
      } catch { /* keep last known — never invent */ }
    };
    load();
    const id = setInterval(load, SUMMARY_POLL_MS);
    return () => { active = false; clearInterval(id); };
  }, []);
  void live.tick;
  const freshAt = summary?.refreshed_at ?? summary?.updated_at ?? null;
  const { tier, elapsedMs } = freshnessTier(freshAt ? Date.parse(freshAt) : NaN, summary?.expected_interval_s ?? 0, Date.now());
  const wall = mounted ? new Date().toLocaleTimeString('en-US', { hour12: false }) : '--:--:--';
  const view = tier === 'stale'
    ? { label: `STALE ${elapsedMs !== null ? syncLabel(elapsedMs) : ''}`.trim(), color: 'var(--miss)', weight: 700 }
    : tier === 'amber'
      ? { label: `SYNC ${elapsedMs !== null ? syncLabel(elapsedMs) : '—'}`, color: 'var(--amber)', weight: 700 }
      : { label: wall, color: 'var(--text-1)', weight: 400 };
  return (
    <span className={`mono ${className}`} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 10.5, color: view.color, fontWeight: view.weight, letterSpacing: '0.04em', flexShrink: 0 }}>
      <span style={{ width: 6, height: 6, borderRadius: '50%', background: view.color, flexShrink: 0 }} />
      {view.label}
    </span>
  );
}
