/**
 * TokenMeter.jsx
 * Owns: rendering token usage, running cost, and optional budget cap with warning states.
 * Does NOT own: routing, model inference, actual cost billing.
 *
 * AG-UI events consumed:
 *   TEXT_MESSAGE_CHUNK  { detail: { delta, usage? } }
 *   TOOL_CALL_END       { detail: { usage? } }
 *
 * Props:
 *   model: string          — key from MODEL_PRICING or any string (falls back to custom rate)
 *   usage: { inputTokens, outputTokens }
 *   budgetUsd?: number     — optional cap; shows progress bar when set
 *   mode?: 'compact'|'expanded'   (default: 'compact')
 *   theme?: 'light'|'dark'|'auto'
 *   onBudgetExceeded?: () => void  — fires once when cost >= budgetUsd
 *
 * Zero dependencies beyond React. Export MODEL_PRICING to override pricing.
 */

const { useState, useEffect, useRef, useCallback } = React;

// Built-in pricing table (USD per 1M tokens). Exportable — devs can add custom models.
const MODEL_PRICING = {
  // OpenAI
  'gpt-4o':               { inputPer1M: 2.50,   outputPer1M: 10.00,  label: 'GPT-4o',         provider: 'openai',    color: '#10a37f' },
  'gpt-4o-mini':          { inputPer1M: 0.15,   outputPer1M: 0.60,   label: 'GPT-4o mini',    provider: 'openai',    color: '#10a37f' },
  'o1':                   { inputPer1M: 15.00,  outputPer1M: 60.00,  label: 'o1',              provider: 'openai',    color: '#10a37f' },
  // Anthropic
  'claude-sonnet-4':      { inputPer1M: 3.00,   outputPer1M: 15.00,  label: 'Claude Sonnet 4', provider: 'anthropic', color: '#d4642a' },
  'claude-opus-4':        { inputPer1M: 15.00,  outputPer1M: 75.00,  label: 'Claude Opus 4',  provider: 'anthropic', color: '#d4642a' },
  'claude-haiku':         { inputPer1M: 0.25,   outputPer1M: 1.25,   label: 'Claude Haiku',   provider: 'anthropic', color: '#d4642a' },
  // Google
  'gemini-2.5-pro':       { inputPer1M: 1.25,   outputPer1M: 10.00,  label: 'Gemini 2.5 Pro', provider: 'google',    color: '#4285f4' },
  'gemini-2.5-flash':     { inputPer1M: 0.15,   outputPer1M: 0.60,   label: 'Gemini 2.5 Flash', provider: 'google',  color: '#4285f4' },
};

// Design tokens
const TOKENS = {
  light: {
    bg: '#FAFAF8',
    card: '#FFFFFF',
    border: '#E8E6E0',
    fg: '#1a1a1a',
    fgMuted: '#666',
    accent: '#F5A623',
    success: '#22c55e',
    amber: '#f59e0b',
    red: '#ef4444',
    progressTrack: '#E8E6E0',
    codeBg: '#F3F2EF',
    shadow: '0 2px 12px rgba(0,0,0,0.06)',
  },
  dark: {
    bg: '#111110',
    card: '#1C1C1A',
    border: '#2E2E2C',
    fg: '#F0EEE8',
    fgMuted: '#888',
    accent: '#F5A623',
    success: '#4ade80',
    amber: '#fbbf24',
    red: '#f87171',
    progressTrack: '#2E2E2C',
    codeBg: '#141413',
    shadow: '0 2px 12px rgba(0,0,0,0.4)',
  },
};

// Resolve 'auto' → 'light'|'dark' from matchMedia
function resolveTheme(theme) {
  if (theme !== 'auto') return theme || 'light';
  if (typeof window === 'undefined') return 'light';
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}

// CSS keyframes injected once into <head>
const TM_STYLES = `
@keyframes tm-pulse-amber { 0%,100%{opacity:1;box-shadow:0 0 0 0 rgba(245,158,11,0)} 50%{opacity:0.85;box-shadow:0 0 0 3px rgba(245,158,11,0.25)} }
@keyframes tm-pulse-red   { 0%,100%{opacity:1;box-shadow:0 0 0 0 rgba(239,68,68,0)}  50%{opacity:0.85;box-shadow:0 0 0 4px rgba(239,68,68,0.30)} }
@keyframes tm-countup     { from{opacity:0;transform:translateY(4px)} to{opacity:1;transform:translateY(0)} }
@keyframes tm-fill        { from{width:0} }
@keyframes tm-spin        { to{transform:rotate(360deg)} }
@keyframes tm-fade-in     { from{opacity:0;transform:translateY(3px)} to{opacity:1;transform:translateY(0)} }
`;

let _tmStylesInjected = false;
function injectTMStyles() {
  if (_tmStylesInjected || typeof document === 'undefined') return;
  const el = document.createElement('style');
  el.textContent = TM_STYLES;
  document.head.appendChild(el);
  _tmStylesInjected = true;
}

function formatTokens(n) {
  if (n == null || isNaN(n)) return '0';
  if (n >= 1_000_000) return (n / 1_000_000).toFixed(2) + 'M';
  if (n >= 1_000) return (n / 1_000).toFixed(n >= 100_000 ? 0 : 1) + 'k';
  return n.toLocaleString();
}

function formatCost(usd) {
  if (usd == null || isNaN(usd)) return '$0.0000';
  if (usd >= 1) return '$' + usd.toFixed(4);
  if (usd >= 0.01) return '$' + usd.toFixed(4);
  return '$' + usd.toFixed(6);
}

function computeCost(model, usage) {
  const pricing = MODEL_PRICING[model];
  if (!pricing) return 0;
  const inp = (usage.inputTokens || 0) / 1_000_000 * pricing.inputPer1M;
  const out = (usage.outputTokens || 0) / 1_000_000 * pricing.outputPer1M;
  return inp + out;
}

// Budget fraction → state
function budgetState(fraction) {
  if (fraction >= 1.0) return 'locked';
  if (fraction >= 0.9) return 'red';
  if (fraction >= 0.7) return 'amber';
  return 'green';
}

// Animated number — count-up interpolation on value change
function AnimatedNumber({ value, format, style: styleProp }) {
  const [display, setDisplay] = useState(value);
  const [bump, setBump] = useState(false);
  const prev = useRef(value);

  useEffect(() => {
    if (value === prev.current) return;
    prev.current = value;
    setBump(true);
    // Fast interpolation over 300ms
    const start = display;
    const diff = value - start;
    if (diff === 0) { setBump(false); return; }
    const startTime = performance.now();
    const duration = 280;
    function step(now) {
      const t = Math.min((now - startTime) / duration, 1);
      const eased = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
      setDisplay(start + diff * eased);
      if (t < 1) requestAnimationFrame(step);
      else { setDisplay(value); setBump(false); }
    }
    requestAnimationFrame(step);
  }, [value]);

  return (
    <span
      key={bump ? 'bump' : 'still'}
      style={{
        ...styleProp,
        display: 'inline-block',
        animation: bump ? 'tm-countup 0.18s ease' : 'none',
      }}
    >
      {format ? format(display) : display}
    </span>
  );
}
// Progress bar
function BudgetBar({ fraction, state: bState, tok }) {
  const clampedFraction = Math.min(fraction, 1);
  const colorMap = {
    green:  tok.success,
    amber:  tok.amber,
    red:    tok.red,
    locked: tok.red,
  };
  const color = colorMap[bState] || tok.success;
  const pulse = bState === 'amber' ? 'tm-pulse-amber 2s ease-in-out infinite'
              : bState === 'red' || bState === 'locked' ? 'tm-pulse-red 1.2s ease-in-out infinite'
              : 'none';

  return (
    <div style={{
      height: 4,
      background: tok.progressTrack,
      borderRadius: 2,
      overflow: 'hidden',
      position: 'relative',
    }}>
      <div style={{
        height: '100%',
        width: `${clampedFraction * 100}%`,
        background: color,
        borderRadius: 2,
        transition: 'width 0.5s ease, background 0.4s',
        animation: `tm-fill 0.5s ease, ${pulse}`,
      }} />
    </div>
  );
}
// Lock icon
function LockIcon({ color }) {
  return (
    <svg width="11" height="13" viewBox="0 0 11 13" fill="none" style={{ flexShrink: 0 }}>
      <rect x="1" y="5.5" width="9" height="7" rx="1.5" stroke={color} strokeWidth="1.4"/>
      <path d="M3.5 5.5V3.5a2 2 0 014 0v2" stroke={color} strokeWidth="1.4" strokeLinecap="round"/>
    </svg>
  );
}
// Warning icon
function WarnIcon({ color }) {
  return (
    <svg width="13" height="12" viewBox="0 0 13 12" fill="none" style={{ flexShrink: 0 }}>
      <path d="M6.5 1L12 11H1L6.5 1z" stroke={color} strokeWidth="1.4" strokeLinejoin="round"/>
      <path d="M6.5 5v2.5" stroke={color} strokeWidth="1.4" strokeLinecap="round"/>
      <circle cx="6.5" cy="9" r="0.6" fill={color}/>
    </svg>
  );
}
// Provider accent dot
function ProviderDot({ model }) {
  const info = MODEL_PRICING[model];
  const color = info ? info.color : '#888';
  return (
    <span style={{
      display: 'inline-block',
      width: 6, height: 6, borderRadius: '50%',
      background: color, flexShrink: 0,
      marginRight: 4,
    }} />
  );
}
// TokenMeter (controlled)
function TokenMeter({ model = 'claude-sonnet-4', usage = { inputTokens: 0, outputTokens: 0 }, budgetUsd, mode = 'compact', theme = 'light', onBudgetExceeded }) {
  injectTMStyles();

  const [resolvedTheme, setResolvedTheme] = useState(resolveTheme(theme));
  useEffect(() => {
    if (theme !== 'auto') { setResolvedTheme(theme); return; }
    const mq = window.matchMedia('(prefers-color-scheme: dark)');
    const handler = e => setResolvedTheme(e.matches ? 'dark' : 'light');
    mq.addEventListener('change', handler);
    return () => mq.removeEventListener('change', handler);
  }, [theme]);

  const tok = TOKENS[resolvedTheme] || TOKENS.light;

  const cost = computeCost(model, usage);
  const fraction = budgetUsd > 0 ? cost / budgetUsd : 0;
  const bState = budgetUsd > 0 ? budgetState(fraction) : null;

  // Fire onBudgetExceeded once when locked
  const exceededFired = useRef(false);
  useEffect(() => {
    if (bState === 'locked' && !exceededFired.current) {
      exceededFired.current = true;
      onBudgetExceeded && onBudgetExceeded();
    }
    if (bState !== 'locked') exceededFired.current = false;
  }, [bState, onBudgetExceeded]);

  const pricing = MODEL_PRICING[model];
  const modelLabel = pricing ? pricing.label : model;

  const pulseAnim = bState === 'amber' ? 'tm-pulse-amber 2s ease-in-out infinite'
                  : bState === 'red' || bState === 'locked' ? 'tm-pulse-red 1.2s ease-in-out infinite'
                  : 'none';

  // ── Compact mode (single-line pill) ───────────────────────────────────────
  if (mode === 'compact') {
    const statusColors = { green: tok.success, amber: tok.amber, red: tok.red, locked: tok.red };
    const borderColor = bState ? (statusColors[bState] || tok.border) : tok.border;

    return (
      <div style={{
        display: 'inline-flex',
        alignItems: 'center',
        gap: 8,
        background: tok.card,
        border: `1px solid ${borderColor}`,
        borderRadius: 20,
        padding: '5px 12px',
        boxShadow: tok.shadow,
        fontFamily: "'Space Grotesk', sans-serif",
        fontSize: '0.775rem',
        animation: pulseAnim,
        transition: 'border-color 0.4s',
        userSelect: 'none',
      }}>
        {/* Model label */}
        <ProviderDot model={model} />
        <span style={{ color: tok.fgMuted, fontWeight: 500 }}>{modelLabel}</span>

        {/* Separator */}
        <span style={{ color: tok.border, fontSize: '0.6rem' }}>│</span>

        {/* Tokens */}
        <span style={{ color: tok.fgMuted }}>
          <AnimatedNumber value={usage.inputTokens || 0} format={formatTokens} style={{ color: tok.fg, fontWeight: 600 }} />
          <span style={{ margin: '0 2px', opacity: 0.5 }}>/</span>
          <AnimatedNumber value={usage.outputTokens || 0} format={formatTokens} style={{ color: tok.fg, fontWeight: 600 }} />
          <span style={{ marginLeft: 3, opacity: 0.6 }}>tok</span>
        </span>

        {/* Separator */}
        <span style={{ color: tok.border, fontSize: '0.6rem' }}>│</span>

        {/* Cost */}
        <AnimatedNumber value={cost} format={formatCost} style={{ color: tok.accent, fontWeight: 700, fontVariantNumeric: 'tabular-nums' }} />

        {/* Budget fraction if set */}
        {budgetUsd > 0 && (
          <>
            <span style={{ color: tok.fgMuted, opacity: 0.6 }}>/ {formatCost(budgetUsd)}</span>
            {bState === 'locked' && <LockIcon color={tok.red} />}
            {bState === 'red' && <WarnIcon color={tok.red} />}
            {bState === 'amber' && <WarnIcon color={tok.amber} />}
          </>
        )}
      </div>
    );
  }

  // ── Expanded mode (card with breakdown) ───────────────────────────────────
  const statusColors = { green: tok.success, amber: tok.amber, red: tok.red, locked: tok.red };
  const borderColor = bState ? (statusColors[bState] || tok.border) : tok.border;

  return (
    <div style={{
      background: tok.card,
      border: `1px solid ${borderColor}`,
      borderRadius: 12,
      padding: '1.1rem 1.25rem',
      boxShadow: tok.shadow,
      fontFamily: "'Space Grotesk', sans-serif",
      animation: `tm-fade-in 0.2s ease, ${pulseAnim}`,
      transition: 'border-color 0.4s',
      minWidth: 240,
    }}>
      {/* Header */}
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '0.85rem' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <ProviderDot model={model} />
          <span style={{ fontWeight: 700, fontSize: '0.875rem', color: tok.fg }}>{modelLabel}</span>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          {bState === 'locked' && (
            <span style={{ display: 'flex', alignItems: 'center', gap: 4, color: tok.red, fontSize: '0.7rem', fontWeight: 700 }}>
              <LockIcon color={tok.red} /> BUDGET LOCKED
            </span>
          )}
          {bState === 'red' && !bState.includes('locked') && (
            <span style={{ display: 'flex', alignItems: 'center', gap: 4, color: tok.red, fontSize: '0.7rem', fontWeight: 700 }}>
              <WarnIcon color={tok.red} /> NEAR LIMIT
            </span>
          )}
          {bState === 'amber' && (
            <span style={{ display: 'flex', alignItems: 'center', gap: 4, color: tok.amber, fontSize: '0.7rem', fontWeight: 700 }}>
              <WarnIcon color={tok.amber} /> BUDGET WARNING
            </span>
          )}
        </div>
      </div>

      {/* Token row */}
      <div style={{ display: 'flex', gap: '1rem', marginBottom: '0.75rem' }}>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: '0.68rem', color: tok.fgMuted, fontWeight: 600, letterSpacing: '0.06em', textTransform: 'uppercase', marginBottom: 2 }}>Input</div>
          <div style={{ fontSize: '1.15rem', fontWeight: 700, color: tok.fg, fontVariantNumeric: 'tabular-nums' }}>
            <AnimatedNumber value={usage.inputTokens || 0} format={formatTokens} />
          </div>
          {pricing && (
            <div style={{ fontSize: '0.68rem', color: tok.fgMuted, marginTop: 1 }}>
              ${pricing.inputPer1M}/M tok
            </div>
          )}
        </div>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: '0.68rem', color: tok.fgMuted, fontWeight: 600, letterSpacing: '0.06em', textTransform: 'uppercase', marginBottom: 2 }}>Output</div>
          <div style={{ fontSize: '1.15rem', fontWeight: 700, color: tok.fg, fontVariantNumeric: 'tabular-nums' }}>
            <AnimatedNumber value={usage.outputTokens || 0} format={formatTokens} />
          </div>
          {pricing && (
            <div style={{ fontSize: '0.68rem', color: tok.fgMuted, marginTop: 1 }}>
              ${pricing.outputPer1M}/M tok
            </div>
          )}
        </div>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: '0.68rem', color: tok.fgMuted, fontWeight: 600, letterSpacing: '0.06em', textTransform: 'uppercase', marginBottom: 2 }}>Cost</div>
          <div style={{ fontSize: '1.15rem', fontWeight: 700, color: tok.accent, fontVariantNumeric: 'tabular-nums' }}>
            <AnimatedNumber value={cost} format={formatCost} />
          </div>
          {budgetUsd > 0 && (
            <div style={{ fontSize: '0.68rem', color: tok.fgMuted, marginTop: 1 }}>
              of {formatCost(budgetUsd)} cap
            </div>
          )}
        </div>
      </div>

      {/* Budget bar */}
      {budgetUsd > 0 && (
        <div style={{ marginTop: '0.5rem' }}>
          <BudgetBar fraction={fraction} state={bState} tok={tok} />
          <div style={{
            display: 'flex', justifyContent: 'space-between',
            fontSize: '0.68rem', color: tok.fgMuted, marginTop: 4,
          }}>
            <span>{Math.round(fraction * 100)}% of budget</span>
            <span>{formatCost(Math.max(0, budgetUsd - cost))} remaining</span>
          </div>
        </div>
      )}
    </div>
  );
}

// TokenMeterContainer (AG-UI auto-wiring)
/**
 * Wraps TokenMeter and listens to AG-UI events for token deltas.
 *
 * Supported events:
 *   TEXT_MESSAGE_CHUNK  { detail: { delta?, usage?: { inputTokens, outputTokens } } }
 *   TOOL_CALL_END       { detail: { usage?: { inputTokens, outputTokens } } }
 *
 * Props: same as TokenMeter, plus:
 *   eventSource: EventTarget | EventEmitter
 *   initialUsage?: { inputTokens, outputTokens }
 */
function TokenMeterContainer({
  eventSource,
  model = 'claude-sonnet-4',
  initialUsage = { inputTokens: 0, outputTokens: 0 },
  budgetUsd,
  mode = 'compact',
  theme = 'light',
  onBudgetExceeded,
}) {
  const [usage, setUsage] = useState(initialUsage);

  const applyUsage = useCallback((patch) => {
    if (!patch) return;
    setUsage(prev => ({
      inputTokens: patch.inputTokens != null ? patch.inputTokens : prev.inputTokens,
      outputTokens: patch.outputTokens != null ? patch.outputTokens : prev.outputTokens,
    }));
  }, []);

  useEffect(() => {
    if (!eventSource) return;

    function onChunk(e) {
      const d = e.detail || e;
      if (d.usage) applyUsage(d.usage);
    }

    function onToolEnd(e) {
      const d = e.detail || e;
      if (d.usage) applyUsage(d.usage);
    }

    if (typeof eventSource.addEventListener === 'function') {
      eventSource.addEventListener('TEXT_MESSAGE_CHUNK', onChunk);
      eventSource.addEventListener('TOOL_CALL_END', onToolEnd);
      return () => {
        eventSource.removeEventListener('TEXT_MESSAGE_CHUNK', onChunk);
        eventSource.removeEventListener('TOOL_CALL_END', onToolEnd);
      };
    }

    if (typeof eventSource.on === 'function') {
      eventSource.on('TEXT_MESSAGE_CHUNK', onChunk);
      eventSource.on('TOOL_CALL_END', onToolEnd);
      return () => {
        eventSource.off('TEXT_MESSAGE_CHUNK', onChunk);
        eventSource.off('TOOL_CALL_END', onToolEnd);
      };
    }
  }, [eventSource, applyUsage]);

  return (
    <TokenMeter
      model={model}
      usage={usage}
      budgetUsd={budgetUsd}
      mode={mode}
      theme={theme}
      onBudgetExceeded={onBudgetExceeded}
    />
  );
}

// Exports
if (typeof module !== 'undefined') {
  module.exports = { TokenMeter, TokenMeterContainer, MODEL_PRICING };
}
