'use client';

import { useEffect, useRef, useState } from 'react';

type TickerItem = {
  tag: string;
  text: string;
  /** Tag color (default amber). */
  color?: string;
  /** Amber glow on the tag. */
  glow?: boolean;
  /** Movement delta, e.g. "▲+1.5" (green) or "▼-0.5" (red). */
  delta?: string;
};

type TickerProps = {
  items: TickerItem[];
  height?: number;
  /** Poll /api/ticker for live snapshot events (Session 45). Default true. */
  live?: boolean;
  /** Poll interval ms (default 30s). */
  pollMs?: number;
};

const TAG_COLORS: Record<string, string> = {
  'A+': 'var(--g-ap)', A: 'var(--g-a)', READ: 'var(--g-a)', SCAN: 'var(--g-a)',
  MOVE: 'var(--amber)', CASCADE: 'var(--amber)', ALERT: 'var(--text-0)',
};

// Spec §3 — fewer than 4 real items → no ticker. An almost-empty marquee
// looping the same item reads as fake; hide the bar instead.
const MIN_ITEMS = 4;

// DS5 (DESIGN-SPEC Part 4) — the ticker RESTS this long on each item so it can
// be READ. Mirrors the --ticker-hold token in globals.css (kept in sync; a unit
// test cross-checks both are ≥4000ms). No one-off duration literals elsewhere.
const TICKER_HOLD_MS = 4200;

/**
 * The ranked "top moves" strip (§5) — DS5 rebuild to PUNCTUATED STILLNESS.
 *
 * The old continuous marquee (ticker-scroll, motion at all times) is retired:
 * constant motion reads cheap (Part 0 law #1). The ticker now RESTS on one
 * ranked item for --ticker-hold (≥4s), advancing with a sub-200ms cross-fade
 * and a change-only green pulse — the terminal reacting, then still. It is the
 * only motion in the ticker; the header's single idle proof-of-life is the
 * heartbeat's one live-dot (this bar no longer renders a competing pulse).
 *
 * Session 45 — still polls /api/ticker for live snapshot exhaust. Session 57 —
 * real items only (callers pass []); <MIN_ITEMS → renders nothing. Visibility
 * is published as `--ticker-h` on <html> so the fixed header stack collapses
 * with it. prefers-reduced-motion → a static ranked list, zero motion.
 */
export default function Ticker({ items, height = 34, live = true, pollMs = 30_000 }: TickerProps) {
  const [feed, setFeed] = useState<TickerItem[] | null>(null);
  const [idx, setIdx] = useState(0);
  const [pulse, setPulse] = useState(false);
  const [reduced, setReduced] = useState(false);

  // Respect prefers-reduced-motion: no rotation, no pulse — a static strip.
  useEffect(() => {
    if (typeof window === 'undefined' || !window.matchMedia) return;
    const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
    const apply = () => setReduced(mq.matches);
    apply();
    mq.addEventListener?.('change', apply);
    return () => mq.removeEventListener?.('change', apply);
  }, []);

  useEffect(() => {
    if (!live) return;
    let active = true;
    let lastHead = '';
    const load = async () => {
      try {
        const r = await fetch('/api/ticker', { cache: 'no-store' });
        if (!r.ok) return;
        const data = (await r.json()) as { items?: TickerItem[] };
        if (active && Array.isArray(data.items) && data.items.length > 0) {
          const head = `${data.items[0]?.tag}|${data.items[0]?.text}`;
          if (lastHead && head !== lastHead) {
            // A fresh head event — reset to the top and pulse ONCE (change-only,
            // the DATA-UPDATE motion category, never idle).
            setIdx(0);
            setPulse(true);
            setTimeout(() => { if (active) setPulse(false); }, 600);
          }
          lastHead = head;
          setFeed(data.items.map((it) => ({ ...it, color: it.color || TAG_COLORS[it.tag] || 'var(--amber)' })));
        }
      } catch {
        /* keep the current items on failure — never blank the bar */
      }
    };
    load();
    const id = setInterval(load, pollMs);
    return () => { active = false; clearInterval(id); };
  }, [live, pollMs]);

  const display = feed && feed.length > 0 ? feed : items;
  const visible = display.length >= MIN_ITEMS;

  // Publish the bar's height so the fixed-header consumers (layout main
  // paddingTop, Slate sticky top) stay in sync whether or not the bar shows.
  useEffect(() => {
    if (typeof document === 'undefined') return;
    document.documentElement.style.setProperty('--ticker-h', visible ? `${height}px` : '0px');
    return () => { document.documentElement.style.setProperty('--ticker-h', '0px'); };
  }, [visible, height]);

  // The rotation: HOLD each item for TICKER_HOLD_MS, then advance. Disabled
  // entirely under reduced motion or with a single item — punctuated stillness,
  // not a metronome.
  const len = display.length;
  useEffect(() => {
    if (reduced || len <= 1) return;
    const id = setInterval(() => {
      setIdx((i) => (i + 1) % len);
      setPulse(true);
      setTimeout(() => setPulse(false), 600);
    }, TICKER_HOLD_MS);
    return () => clearInterval(id);
  }, [reduced, len]);

  const heldRef = useRef(idx);
  heldRef.current = idx;
  if (!visible) return null;

  const renderItem = (it: TickerItem, key: number, rank?: number) => (
    <span
      key={key}
      className="mono"
      style={{ display: 'inline-flex', alignItems: 'center', gap: 8, padding: '0 22px', fontSize: 12, letterSpacing: '0.04em', color: 'var(--text-1)' }}
    >
      {rank != null && (
        <span className="mono" style={{ color: 'var(--text-2)', fontWeight: 700, fontSize: 10, minWidth: 30 }}>
          {rank}/{len}
        </span>
      )}
      <span
        style={{
          color: it.color || 'var(--amber)',
          textShadow: it.glow || it.tag === 'A+' ? 'var(--amber-glow)' : 'none',
          fontWeight: 700,
        }}
      >
        {it.tag}
      </span>
      <span style={{ color: 'var(--text-0)' }}>{it.text}</span>
      {it.delta && (
        <span style={{ color: it.delta.startsWith('▲') ? 'var(--g-a)' : 'var(--miss)', fontWeight: 700 }}>{it.delta}</span>
      )}
    </span>
  );

  return (
    <div
      className="scanlines"
      style={{ height, overflow: 'hidden', background: 'var(--bg-1)', borderTop: '1px solid var(--border)', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', position: 'relative' }}
    >
      {/* Static "TOP MOVES" anchor — a label, not a pulsing dot. The single
          idle proof-of-life lives in the heartbeat below (one live-dot). */}
      <div
        className="mono"
        style={{
          flexShrink: 0, display: 'flex', alignItems: 'center', gap: 6, padding: '0 14px 0 12px',
          alignSelf: 'stretch', background: 'var(--bg-1)', borderRight: '1px solid var(--border)',
          fontSize: 10, fontWeight: 700, letterSpacing: '0.1em', color: 'var(--g-a)',
        }}
        aria-hidden
      >
        TOP MOVES
      </div>

      {reduced ? (
        // Reduced motion — the full ranked list, static, no animation at all.
        <div className="ticker-rest" style={{ flex: 1 }}>
          {display.map((it, i) => renderItem(it, i))}
        </div>
      ) : (
        // Resting rotator — ONE item held for ≥4s, change-only cross-fade + pulse.
        <div
          key={idx}
          className={`ticker-rest ticker-item-enter${pulse ? ' ticker-pulse' : ''}`}
          style={{ flex: 1 }}
          aria-live="polite"
        >
          {renderItem(display[idx % len], idx, (idx % len) + 1)}
        </div>
      )}

      <div style={{ position: 'absolute', right: 0, top: 0, bottom: 0, width: 48, background: 'linear-gradient(270deg, var(--bg-1), transparent)', zIndex: 2 }} />
    </div>
  );
}
