// Dashboard pages - internal app shell with sidebar + dashboard views.
// Production-backed views load from /api/dashboard and render empty states
// when an account has no jobs, usage, invoices, or settings yet.

// --- Dashboard data helpers --------------------------------------------------
const DASH_SESSION_KEY = 'miq_dashboard_session';
const isLocalDashboard = () => ['localhost', '127.0.0.1'].includes(window.location.hostname);
const dashApiRoot = () => isLocalDashboard() ? '/api/dashboard' : '/api/momentiq/api/dashboard';
const dashAuthRoot = () => isLocalDashboard() ? '/api/auth' : '/api/momentiq/api/auth';
const dashSession = () => {
  try { return JSON.parse(localStorage.getItem(DASH_SESSION_KEY) || 'null'); }
  catch { return null; }
};
const dashSaveSession = (payload, mode) => {
  const account = payload?.account || {};
  const user = payload?.user || {};
  localStorage.setItem(DASH_SESSION_KEY, JSON.stringify({
    signedIn: true,
    mode,
    orgName: account.name || 'MomentIQ account',
    orgId: account.id || account.account_id || 'acct_pending',
    userEmail: user.email || '',
    userName: user.name || '',
  }));
  window.location.reload();
};
const displayNameFromSession = (session) => {
  if (!session) return 'there';
  if (session.userName) return session.userName.split(/\s+/)[0];
  if (session.userEmail) return session.userEmail.split('@')[0];
  return 'there';
};
const dashSignOut = async () => {
  try { await fetch(dashAuthRoot() + '/logout', { method: 'POST', credentials: 'include' }); }
  catch (_err) {}
  localStorage.removeItem(DASH_SESSION_KEY);
  window.location.href = 'index.html';
};
const dashAuthApi = async (path, body) => {
  const r = await fetch(dashAuthRoot() + path, {
    method: 'POST',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body || {}),
  });
  const json = await r.json().catch(() => ({}));
  if (!r.ok) throw Object.assign(new Error(json.error?.message || r.statusText), { payload: json });
  return json;
};
const dashAuthGet = async (path) => {
  const r = await fetch(dashAuthRoot() + path, { method: 'GET', credentials: 'include' });
  const json = await r.json().catch(() => ({}));
  if (!r.ok) throw Object.assign(new Error(json.error?.message || r.statusText), { payload: json });
  return json;
};
const dashLoadGoogleIdentity = () => new Promise((resolve, reject) => {
  if (window.google?.accounts?.id) return resolve(window.google);
  const existing = document.querySelector('script[data-google-identity]');
  if (existing) {
    existing.addEventListener('load', () => resolve(window.google), { once: true });
    existing.addEventListener('error', reject, { once: true });
    return;
  }
  const script = document.createElement('script');
  script.src = 'https://accounts.google.com/gsi/client';
  script.async = true;
  script.defer = true;
  script.dataset.googleIdentity = 'true';
  script.onload = () => resolve(window.google);
  script.onerror = reject;
  document.head.appendChild(script);
});
const dashApi = async (path, opts = {}) => {
  const r = await fetch(dashApiRoot() + path, {
    method: opts.method || 'GET',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: opts.body ? JSON.stringify(opts.body) : undefined,
  });
  const json = await r.json().catch(() => ({}));
  if (!r.ok) throw Object.assign(new Error(json.error?.message || r.statusText), { payload: json });
  return json;
};
const useDashApi = (path) => {
  const [state, setState] = React.useState({ loading: true, data: null, error: null });
  React.useEffect(() => {
    let alive = true;
    dashApi(path)
      .then(data => alive && setState({ loading: false, data, error: null }))
      .catch(error => alive && setState({ loading: false, data: null, error }));
    return () => { alive = false; };
  }, [path]);
  return state;
};
const dashTrack = (name, props) => {
  if (window.MIQAnalytics) {
    const session = dashSession() || {};
    window.MIQAnalytics.track(name, Object.assign({ account_id: session.orgId || 'anonymous' }, props || {}));
  }
};
const formatMoney = (value) => `$${Number(value || 0).toFixed(2)}`;
const formatCount = (value) => Number(value || 0).toLocaleString();
const formatSeconds = (seconds) => `${Number(seconds || 0).toLocaleString()}s`;
const formatAgeMs = (ms) => {
  const n = Number(ms);
  if (!Number.isFinite(n)) return 'never';
  if (n < 60000) return `${Math.max(1, Math.round(n / 1000))}s ago`;
  if (n < 3600000) return `${Math.round(n / 60000)}m ago`;
  return `${Math.round(n / 3600000)}h ago`;
};
const formatUnit = (unit) => String(unit || '').replace(/_/g, ' ') || '-';
const rateDecimals = (value) => {
  const n = Number(value || 0);
  return Math.abs((n * 100) - Math.round(n * 100)) > 1e-9 || n < 0.01 ? 3 : 2;
};
const formatRate = (row) => row.unitRate ? String.fromCharCode(36) + Number(row.unitRate).toFixed(rateDecimals(row.unitRate)) + ' / ' + formatUnit(row.pricingUnit) : '-';
const endpointDocHref = (endpoint) => endpoint ? `docs-${String(endpoint).replace('/', '-')}.html` : 'docs.html';
const endpointPath = (endpoint) => endpoint ? `/v1/${endpoint}` : '-';
const mediaName = (value) => {
  if (!value || value === '-') return '-';
  try {
    const u = new URL(value);
    const bits = u.pathname.split('/').filter(Boolean);
    return decodeURIComponent(bits[bits.length - 1] || u.hostname);
  } catch (_err) {
    return String(value).split(/[\\/]/).pop() || String(value);
  }
};
const jobOk = (status) => ['completed', 'succeeded'].includes(status);
const normalizeUsageRow = (row) => ({
  endpoint: row.endpoint || row.id || '',
  apiKeyId: row.api_key_id || row.apiKeyId || '',
  apiKeyName: row.api_key_name || row.apiKeyName || '',
  apiKeyPrefix: row.api_key_prefix || row.apiKeyPrefix || '',
  environment: row.environment || '',
  status: row.status || '',
  requests: Number(row.requests || 0),
  billableUnits: Number(row.billable_units ?? row.billableUnits ?? row.media_minutes ?? 0),
  minutes: Number(row.minutes ?? row.media_minutes ?? row.billable_units ?? (row.media_seconds ? row.media_seconds / 60 : 0)),
  sourceMinutes: Number(row.source_media_minutes ?? row.sourceMediaMinutes ?? 0),
  cost: Number(row.cost ?? row.cost_usd ?? 0),
  errors: Number(row.errors ?? row.failed_requests ?? 0),
  pricingUnit: row.pricing_unit || row.pricingUnit || row.billing_unit || '',
  unitLabel: row.unit_label || row.unitLabel || '',
  unitRate: Number(row.unit_rate_usd ?? row.unitRateUsd ?? row.price_usd ?? 0),
  averageCost: Number(row.average_cost_usd ?? 0),
});
const normalizeJob = (row) => ({
  id: row.id || row.job_id || '',
  endpoint: row.endpoint || '',
  media: row.media || row.media_url || '-',
  status: row.status || 'queued',
  duration: Number(row.duration_seconds || row.media_seconds || 0),
  estimated: Number(row.estimated_price_usd || 0),
  final: row.final_price_usd == null ? null : Number(row.final_price_usd || 0),
  created: row.created_at_relative || row.created_at || '',
  error: row.error || null,
});
const safeKeyPrefix = (val) => {
  if (typeof val !== 'string') return undefined;
  const m = val.match(/^([a-z]+_[a-z]+_[a-z0-9]{2,8})/i);
  return m ? m[1] : val.slice(0, 12);
};
const keyEnvFromPrefix = (prefix) => (prefix && prefix.indexOf('_live_') >= 0) ? 'live'
                                   : (prefix && prefix.indexOf('_test_') >= 0) ? 'test'
                                   : 'unknown';
const DataBadge = ({ live }) => (
  <span title={live ? 'Loaded from dashboard API.' : 'Waiting for dashboard API data.'} style={{
    fontFamily: 'JetBrains Mono, ui-monospace, Menlo, monospace', fontSize: 10,
    padding: '2px 7px', borderRadius: 999,
    background: live ? '#e7f4ec' : '#eef0f2',
    color: live ? '#1e6b3a' : '#3b424b',
    textTransform: 'uppercase', letterSpacing: 0.7,
    border: live ? '1px solid #b8dfc6' : '1px solid #d7dce1',
  }}>{live ? 'live data' : 'empty state'}</span>
);

const copyTextToClipboard = async (text) => {
  if (navigator.clipboard?.writeText) {
    await navigator.clipboard.writeText(text);
    return;
  }
  const area = document.createElement('textarea');
  area.value = text;
  area.style.position = 'fixed';
  area.style.opacity = '0';
  document.body.appendChild(area);
  area.select();
  document.execCommand('copy');
  document.body.removeChild(area);
};

const AgentSetupPanel = ({ keys = [] }) => {
  const [copied, setCopied] = React.useState('');
  const activeKeys = keys.filter(k => (k.status || (k.revoked_at ? 'revoked' : 'active')) !== 'revoked');
  const mcpUrl = 'https://api.momentiq.dev/mcp';
  const mcpConfig = [
    '{',
    '  "mcpServers": {',
    '    "momentiq": {',
    `      "url": "${mcpUrl}",`,
    '      "headers": {',
    '        "Authorization": "Bearer ${MIQ_CONNECTOR_TOKEN}"',
    '      }',
    '    }',
    '  }',
    '}',
  ].join('\n');
  const agentPrompt = [
    'Build a media workflow using MomentIQ.',
    '',
    'MomentIQ finds useful moments in video and audio.',
    'For remote MCP, use a revocable MIQ_CONNECTOR_TOKEN created in the MomentIQ dashboard.',
    'For backend API work, use MOMENTIQ_API_KEY from server-side environment variables only.',
    `If remote MCP is available, connect to ${mcpUrl} with Authorization: Bearer \${MIQ_CONNECTOR_TOKEN}.`,
    'Never expose the key in browser code.',
    '',
    'Goal: [describe the user workflow, such as upload a podcast and return 3 funny clips].',
    'Show upload progress, estimated cost, job progress, output preview, and download links.',
    'Use async jobs for long media: create a job, then poll until completed or failed.',
    '',
    'Agent setup docs: https://momentiq.dev/docs-ai-agents.html',
    'Agent setup JSON: https://momentiq.dev/ai-agent-setup.json',
  ].join('\n');
  const betaSmokePrompt = [
    'Run a MomentIQ beta smoke test.',
    'Use MIQ_CONNECTOR_TOKEN for remote MCP, or MOMENTIQ_API_KEY from server-side environment variables for backend API tests.',
    'First call momentiq.run_agent_diagnostics and momentiq.get_account_limits.',
    'Then upload or use a public test media_url.',
    'Plan the energy_to_clip workflow with momentiq.create_workflow_job.',
    'Queue only the first step, poll with momentiq.get_job, then call momentiq.continue_workflow_job after each completed job.',
    'Show me the final output URL, estimated vs final cost, usage summary, and any errors with request_id/job_id.',
    'After the test, remind me to revoke the test key if I do not need it anymore.',
  ].join('\n');
  const copy = async (kind, text) => {
    try {
      await copyTextToClipboard(text);
      setCopied(kind);
      setTimeout(() => setCopied(''), 1600);
      dashTrack('agent_setup_copied', { kind });
    } catch (_err) {
      setCopied('copy_failed');
    }
  };
  const miniButton = (label, kind, text) => (
    <button type="button" onClick={() => copy(kind, text)} style={{
      border: 0, background: 'transparent', padding: 0,
      cursor: 'pointer',
    }}>
      <Btn size={12}>{copied === kind ? 'Copied' : label}</Btn>
    </button>
  );
  return (
    <div style={{ ...dashStyles.card, marginTop: 14, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 18, alignItems: 'start' }}>
      <div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6, flexWrap: 'wrap' }}>
          <div style={{ fontFamily: wf.sans, fontSize: 16, fontWeight: 600 }}>AI Agent Setup</div>
          <span style={{ fontFamily: wf.mono, fontSize: 10.5, padding: '2px 7px', borderRadius: 999, background: 'var(--accent-soft)', color: 'var(--accent)', textTransform: 'uppercase', letterSpacing: 0.6 }}>Remote MCP</span>
        </div>
        <div style={{ fontFamily: wf.sans, fontSize: 12.8, color: wf.ink3, lineHeight: 1.55, marginBottom: 12 }}>
          Give Claude, Cursor, Replit, Lovable, or another agent a clean tool connection instead of asking it to click around the website. Start with a scoped test key, a hard spend cap, and the beta smoke test.
        </div>
        <div style={{ display: 'grid', gap: 8 }}>
          <div>
            <div style={{ fontFamily: wf.mono, fontSize: 10.5, color: wf.ink3, textTransform: 'uppercase', letterSpacing: 1 }}>MCP URL</div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 4, flexWrap: 'wrap' }}>
              <code style={{ fontFamily: wf.mono, fontSize: 12.5, color: wf.ink, background: wf.cardAlt, border: `1px solid ${wf.line}`, borderRadius: 6, padding: '6px 8px' }}>{mcpUrl}</code>
              {miniButton('Copy URL', 'mcp_url', mcpUrl)}
            </div>
          </div>
          <div>
            <div style={{ fontFamily: wf.mono, fontSize: 10.5, color: wf.ink3, textTransform: 'uppercase', letterSpacing: 1 }}>Auth</div>
            <div style={{ fontFamily: wf.sans, fontSize: 12.5, color: wf.ink3, marginTop: 4 }}>
              For public ChatGPT/Claude connectors, use <code style={{ fontFamily: wf.mono }}>Authorization: Bearer {'${MIQ_CONNECTOR_TOKEN}'}</code>. Use normal API keys for backend integrations.
            </div>
          </div>
          <div>
            <div style={{ fontFamily: wf.mono, fontSize: 10.5, color: wf.ink3, textTransform: 'uppercase', letterSpacing: 1 }}>Active key prefixes</div>
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 5 }}>
              {activeKeys.length ? activeKeys.slice(0, 4).map(k => (
                <span key={k.id || k.prefix} style={{ fontFamily: wf.mono, fontSize: 11, color: wf.ink2, background: wf.cardAlt, border: `1px solid ${wf.line}`, borderRadius: 999, padding: '3px 7px' }}>{k.prefix || safeKeyPrefix(k.full_secret_one_time)}...</span>
              )) : <span style={{ fontFamily: wf.sans, fontSize: 12.5, color: wf.ink3 }}>Create a scoped key before connecting an agent.</span>}
            </div>
          </div>
        </div>
        <div style={{ display: 'flex', gap: 8, marginTop: 14, flexWrap: 'wrap' }}>
          <a href="dashboard-api-keys.html#connectors" style={{ textDecoration: 'none' }}><Btn primary size={12}>Create connector token</Btn></a>
          <a href="dashboard-settings.html" style={{ textDecoration: 'none' }}><Btn size={12}>Set spend cap</Btn></a>
          <a href="docs-ai-agents.html" style={{ textDecoration: 'none' }}><Btn size={12}>Setup docs</Btn></a>
          <a href="docs-ai-agents.html#beta-test" style={{ textDecoration: 'none' }}><Btn size={12}>Beta smoke test</Btn></a>
        </div>
      </div>
      <div style={{ display: 'grid', gap: 10 }}>
        <div style={{ background: '#0d1014', border: `1px solid ${wf.darkLine}`, borderRadius: 10, padding: 12, color: wf.card }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', gap: 10, alignItems: 'center', marginBottom: 8 }}>
            <div style={{ fontFamily: wf.mono, fontSize: 10.5, color: wf.darkInk3, textTransform: 'uppercase', letterSpacing: 1 }}>MCP config</div>
            {miniButton('Copy config', 'mcp_config', mcpConfig)}
          </div>
          <pre style={{ margin: 0, whiteSpace: 'pre-wrap', fontFamily: wf.mono, fontSize: 11.5, lineHeight: 1.45, color: wf.darkInk2 }}>{mcpConfig}</pre>
        </div>
        <div style={{ background: wf.cardAlt, border: `1px solid ${wf.line}`, borderRadius: 10, padding: 12 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', gap: 10, alignItems: 'center', marginBottom: 8 }}>
            <div style={{ fontFamily: wf.mono, fontSize: 10.5, color: wf.ink3, textTransform: 'uppercase', letterSpacing: 1 }}>Prompt</div>
            {miniButton('Copy prompt', 'agent_prompt', agentPrompt)}
          </div>
          <div style={{ fontFamily: wf.sans, fontSize: 12.5, color: wf.ink3, lineHeight: 1.5 }}>
            Copy this into Claude, Cursor, Replit, or Lovable after creating a connector token or adding <code style={{ fontFamily: wf.mono }}>MOMENTIQ_API_KEY</code> to server-side backend code.
          </div>
        </div>
        <div style={{ background: wf.cardAlt, border: `1px solid ${wf.line}`, borderRadius: 10, padding: 12 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', gap: 10, alignItems: 'center', marginBottom: 8 }}>
            <div style={{ fontFamily: wf.mono, fontSize: 10.5, color: wf.ink3, textTransform: 'uppercase', letterSpacing: 1 }}>Beta smoke test</div>
            {miniButton('Copy test prompt', 'beta_smoke_prompt', betaSmokePrompt)}
          </div>
          <div style={{ fontFamily: wf.sans, fontSize: 12.5, color: wf.ink3, lineHeight: 1.5 }}>
            Before trusting an agent with live media, verify diagnostics, limits, upload, workflow continuation, usage reporting, and key revocation.
          </div>
        </div>
        {copied === 'copy_failed' && <div style={{ fontFamily: wf.sans, fontSize: 12, color: '#992b2b' }}>Could not copy automatically. Select the text manually.</div>}
      </div>
    </div>
  );
};

const DashAuthGate = () => {
  const initialAuthMode = (() => {
    try { return new URLSearchParams(window.location.search).get('auth') === 'login' ? 'login' : 'signup'; }
    catch (_err) { return 'signup'; }
  })();
  const [mode, setMode] = React.useState(initialAuthMode);
  const [form, setForm] = React.useState({ email: '', password: '', name: '', organization: '' });
  const [authConfig, setAuthConfig] = React.useState(null);
  const [error, setError] = React.useState('');
  const [loading, setLoading] = React.useState(false);
  const googleRef = React.useRef(null);
  React.useEffect(() => {
    let alive = true;
    dashAuthGet('/config')
      .then(data => alive && setAuthConfig(data))
      .catch(() => alive && setAuthConfig({ providers: { google: { configured: false }, github: { configured: false } } }));
    return () => { alive = false; };
  }, []);
  React.useEffect(() => {
    const clientId = authConfig?.providers?.google?.client_id;
    if (!clientId || !googleRef.current) return undefined;
    let cancelled = false;
    googleRef.current.innerHTML = '';
    dashLoadGoogleIdentity()
      .then(() => {
        if (cancelled || !window.google?.accounts?.id || !googleRef.current) return;
        window.google.accounts.id.initialize({
          client_id: clientId,
          callback: async (response) => {
            setError('');
            setLoading(true);
            try {
              const payload = await dashAuthApi('/google', { credential: response.credential });
              dashSaveSession(payload, 'google');
            } catch (err) {
              setError(err?.payload?.error?.message || err.message || 'Google sign-in failed.');
            } finally {
              setLoading(false);
            }
          },
        });
        window.google.accounts.id.renderButton(googleRef.current, {
          theme: 'outline',
          size: 'large',
          type: 'standard',
          text: mode === 'signup' ? 'signup_with' : 'signin_with',
          shape: 'rectangular',
          width: Math.min(400, googleRef.current.offsetWidth || 400),
        });
      })
      .catch(() => setError('Could not load Google sign-in. Check browser blockers and provider configuration.'));
    return () => { cancelled = true; };
  }, [authConfig?.providers?.google?.client_id, mode]);
  const field = (key, label, type = 'text') => (
    <label style={{ display: 'grid', gap: 6, fontFamily: wf.sans, fontSize: 12.5, color: wf.ink3 }}>
      {label}
      <input type={type} value={form[key]} onChange={e => setForm({ ...form, [key]: e.target.value })} style={{
        border: `1px solid ${wf.line}`, borderRadius: 7, padding: '10px 11px',
        fontFamily: wf.sans, fontSize: 14, color: wf.ink, background: '#fff',
      }} />
    </label>
  );
  const submit = async (e) => {
    e.preventDefault();
    setError('');
    setLoading(true);
    try {
      const payload = mode === 'signup'
        ? await dashAuthApi('/signup', form)
        : await dashAuthApi('/login', { email: form.email, password: form.password });
      dashSaveSession(payload, mode);
    } catch (err) {
      setError(err?.payload?.error?.message || err.message || 'Could not sign in.');
    } finally {
      setLoading(false);
    }
  };
  return (
    <Page current="Dashboard">
      <div style={{ minHeight: 'calc(100vh - 220px)', display: 'grid', placeItems: 'center', padding: '64px 28px' }}>
        <form onSubmit={submit} style={{ maxWidth: 560, width: '100%', background: wf.card, border: `1px solid ${wf.line}`, borderRadius: 14, padding: 28, boxShadow: '0 24px 60px rgba(11,13,16,0.08)' }}>
          <div style={{ fontFamily: wf.mono, fontSize: 11, letterSpacing: 1.2, color: 'var(--accent)', textTransform: 'uppercase' }}>dashboard access</div>
          <h1 style={{ fontFamily: wf.sans, fontSize: 32, lineHeight: 1.1, fontWeight: 600, margin: '8px 0 10px' }}>{mode === 'signup' ? 'Create your MomentIQ account.' : 'Sign in to MomentIQ.'}</h1>
          <p style={{ fontFamily: wf.sans, fontSize: 14, color: wf.ink3, lineHeight: 1.6, margin: 0 }}>
            The dashboard contains API keys, spend caps, usage history, jobs, billing, and account settings.
          </p>
          <div style={{ display: 'flex', gap: 8, marginTop: 18 }}>
            <span onClick={() => setMode('signup')} style={{ cursor: 'pointer', fontFamily: wf.mono, fontSize: 11, padding: '6px 9px', borderRadius: 999, background: mode === 'signup' ? 'var(--accent-soft)' : wf.cardAlt, color: mode === 'signup' ? 'var(--accent)' : wf.ink3 }}>create account</span>
            <span onClick={() => setMode('login')} style={{ cursor: 'pointer', fontFamily: wf.mono, fontSize: 11, padding: '6px 9px', borderRadius: 999, background: mode === 'login' ? 'var(--accent-soft)' : wf.cardAlt, color: mode === 'login' ? 'var(--accent)' : wf.ink3 }}>sign in</span>
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, marginTop: 14, alignItems: 'center' }}>
            {authConfig?.providers?.google?.configured
              ? <div ref={googleRef} style={{ minHeight: 44 }} />
              : (
                <button type="button" disabled title="Set GOOGLE_CLIENT_ID in Railway API to enable Google sign-in." style={{
                  border: `1px solid ${wf.line}`, borderRadius: 7, padding: '9px 10px',
                  background: wf.cardAlt, color: wf.ink3, fontFamily: wf.sans, fontSize: 12.5,
                  cursor: 'not-allowed', opacity: 0.62,
                }}>Google not configured</button>
              )}
            {authConfig?.providers?.github?.configured
              ? (
                <a
                  href={`${dashAuthRoot()}/github/start?return_to=${encodeURIComponent(window.location.origin + window.location.pathname)}`}
                  style={{
                    border: `1px solid ${wf.line}`, borderRadius: 7, padding: '10px 11px',
                    background: wf.cardAlt, color: wf.ink, fontFamily: wf.sans, fontSize: 13,
                    textDecoration: 'none', cursor: 'pointer', textAlign: 'center',
                  }}
                >Continue with GitHub</a>
              ) : (
                <button type="button" disabled title="Set GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET in Railway API to enable GitHub sign-in." style={{
                  border: `1px solid ${wf.line}`, borderRadius: 7, padding: '9px 10px',
                  background: wf.cardAlt, color: wf.ink3, fontFamily: wf.sans, fontSize: 12.5,
                  cursor: 'not-allowed', opacity: 0.62,
                }}>GitHub not configured</button>
              )}
          </div>
          <div style={{ fontFamily: wf.sans, fontSize: 11.5, color: wf.ink3, marginTop: 7 }}>
            Social sign-in creates or reuses your MomentIQ account. Email/password remains available as a backup.
          </div>
          <div style={{ display: 'grid', gap: 12, marginTop: 18 }}>
            {mode === 'signup' && field('name', 'Name')}
            {mode === 'signup' && field('organization', 'Organization')}
            {field('email', 'Email', 'email')}
            {field('password', 'Password', 'password')}
          </div>
          {error && <div style={{ marginTop: 12, fontFamily: wf.sans, fontSize: 12.5, color: '#992b2b', background: '#fceaea', border: '1px solid #f0c4c4', borderRadius: 7, padding: 10 }}>{error}</div>}
          <div style={{ display: 'flex', gap: 10, marginTop: 20, flexWrap: 'wrap', alignItems: 'center' }}>
            <button type="submit" disabled={loading} style={{ border: 0, cursor: loading ? 'default' : 'pointer', background: 'transparent', padding: 0 }}>
              <Btn primary size={14}>{loading ? 'Working...' : (mode === 'signup' ? 'Create account' : 'Sign in')}</Btn>
            </button>
            <a href="docs.html" style={{ textDecoration: 'none' }}><Btn size={14}>Read docs</Btn></a>
          </div>
        </form>
      </div>
    </Page>
  );
};

const dashStyles = {
  sidebar: { width: 232, background: wf.card, borderRight: `1px solid ${wf.line}`, padding: '18px 12px', flexShrink: 0, minHeight: 'calc(100vh - 56px)' },
  navItem: (active) => ({
    display: 'flex', alignItems: 'center', gap: 10, padding: '8px 12px', borderRadius: 7,
    fontFamily: wf.sans, fontSize: 13, fontWeight: active ? 600 : 500,
    color: active ? wf.ink : wf.ink3,
    background: active ? wf.cardAlt : 'transparent',
    textDecoration: 'none', cursor: 'pointer', marginBottom: 2,
    borderLeft: active ? `2px solid var(--accent)` : '2px solid transparent',
  }),
  body: { flex: 1, padding: '28px 32px', maxWidth: 1180 },
  card: { background: wf.card, border: `1px solid ${wf.line}`, borderRadius: 10, padding: 16 },
};

const DashChrome = ({ current, children }) => {
  const session = dashSession();
  if (!session?.signedIn) return <DashAuthGate />;
  const overviewState = useDashApi('/overview');
  const summary = overviewState.data?.summary || {};
  const mtdSpend = Number(summary.mtd_estimated_charges_usd || 0);
  const cap = Number(summary.spending_cap_usd || 0);
  const capPct = cap ? Math.min(100, Math.round((mtdSpend / cap) * 100)) : 0;
  // Fire dashboard_viewed once per page mount. The `view` property lets
  // funnels distinguish overview vs. keys vs. billing without needing
  // per-view event names.
  React.useEffect(() => {
    dashTrack('dashboard_viewed', { view: current });
    if (current === 'Billing') dashTrack('billing_page_viewed', {});
  }, [current]);

  const navs = [
    ['Overview',          'dashboard.html'],
    ['API keys',          'dashboard-api-keys.html'],
    ['Usage',             'dashboard-usage.html'],
    ['Jobs',              'dashboard-jobs.html'],
    ['Billing',           'dashboard-billing.html'],
    ['Settings',          'dashboard-settings.html'],
    ['Request log',     'dashboard-playground-history.html'],
  ];
  return (
    <Page current="Dashboard">
      <div style={{ display: 'flex' }}>
        <aside style={dashStyles.sidebar}>
          <div style={{ padding: '10px 12px 14px', borderBottom: `1px solid ${wf.line}`, marginBottom: 14 }}>
            <div style={{ fontFamily: wf.mono, fontSize: 10.5, letterSpacing: 1, color: wf.ink3, textTransform: 'uppercase' }}>account</div>
            <div style={{ fontFamily: wf.sans, fontSize: 14, fontWeight: 600, marginTop: 4 }}>{session.orgName || 'MomentIQ account'}</div>
            <div style={{ fontFamily: wf.sans, fontSize: 12.5, color: wf.ink2, marginTop: 3 }}>{session.userEmail}</div>
            <div style={{ fontFamily: wf.mono, fontSize: 10.5, color: wf.ink3, marginTop: 5 }}>{session.orgId || 'org_pending'}</div>
            <span onClick={dashSignOut} style={{ display: 'inline-flex', marginTop: 8, fontFamily: wf.mono, fontSize: 11, color: '#c33', cursor: 'pointer' }}>Sign out</span>
          </div>
          {navs.map(([n,h]) => (
            <a key={n} href={h} style={dashStyles.navItem(current===n)}>
              <span style={{ width: 14, height: 14, borderRadius: 3, background: current===n ? 'var(--accent)' : '#cfd4d9', opacity: current===n ? 1 : 0.6 }} />
              {n}
            </a>
          ))}
          <div style={{ marginTop: 18, padding: '10px 12px', borderRadius: 7, background: wf.cardAlt }}>
            <div style={{ fontFamily: wf.mono, fontSize: 10.5, letterSpacing: 1, color: wf.ink3, textTransform: 'uppercase' }}>last 31 days</div>
            <div style={{ fontFamily: wf.sans, fontSize: 22, fontWeight: 600, marginTop: 4 }}>${mtdSpend.toFixed(2)}</div>
            <div style={{ fontFamily: wf.sans, fontSize: 11.5, color: wf.ink3 }}>cap ${cap.toFixed(2)} - {capPct}% used</div>
            <div style={{ height: 4, background: '#e5e9ed', borderRadius: 999, marginTop: 8, overflow: 'hidden' }}>
              <div style={{ width: `${capPct}%`, height: '100%', background: 'var(--accent)' }} />
            </div>
          </div>
        </aside>
        <div style={dashStyles.body}>{children}</div>
      </div>
    </Page>
  );
};

const Stat = ({ label, value, sub, accent }) => (
  <div style={dashStyles.card}>
    <div style={{ fontFamily: wf.mono, fontSize: 10.5, letterSpacing: 1, color: wf.ink3, textTransform: 'uppercase' }}>{label}</div>
    <div style={{ fontFamily: wf.sans, fontSize: 26, fontWeight: 600, marginTop: 6, color: accent ? 'var(--accent)' : wf.ink }}>{value}</div>
    {sub && <div style={{ fontFamily: wf.sans, fontSize: 12, color: wf.ink3, marginTop: 4 }}>{sub}</div>}
  </div>
);

// Usage bars for endpoint cost and request summaries.
const UsageBars = ({ data }) => {
  const rows = (data || []).map(normalizeUsageRow).filter(d => d.endpoint);
  const max = Math.max(1, ...rows.map(d => d.cost));
  if (!rows.length) {
    return <div style={{ fontFamily: wf.sans, fontSize: 13, color: wf.ink3, padding: '10px 0' }}>Endpoint usage will appear here after real API jobs complete.</div>;
  }
  return (
    <div style={{ display: 'grid', gap: 10 }}>
      {rows.map(d => (
        <div key={d.endpoint}>
          <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 4 }}>
            <a href={endpointDocHref(d.endpoint)} style={{ fontFamily: wf.mono, fontSize: 12, color: wf.ink, textDecoration: 'none' }}>/v1/{d.endpoint}</a>
            <span style={{ fontFamily: wf.mono, fontSize: 12, color: wf.ink3 }}>{formatMoney(d.cost)}</span>
          </div>
          <div style={{ height: 7, borderRadius: 999, background: wf.cardAlt, overflow: 'hidden' }}>
            <div style={{ width: `${Math.max(5, (d.cost / max) * 100)}%`, height: '100%', background: 'var(--accent)' }} />
          </div>
          <div style={{ fontFamily: wf.sans, fontSize: 11.5, color: wf.ink3, marginTop: 3 }}>
            {formatCount(d.requests)} requests - {d.minutes.toFixed(1)} media minutes - {formatRate(d)} - {formatCount(d.errors)} errors
          </div>
        </div>
      ))}
    </div>
  );
};

const DashOverview = () => {
  const overviewState = useDashApi('/overview');
  const keysState = useDashApi('/api-keys');
  const summary = overviewState.data?.summary || {};
  const top = (overviewState.data?.top_endpoints_by_cost || []).map(normalizeUsageRow);
  const recentJobs = (overviewState.data?.recent_jobs || []).map(normalizeJob);
  const recentErrors = overviewState.data?.recent_errors || [];
  const workerHealth = overviewState.data?.worker_health || {};
  const newestWorker = (workerHealth.workers || [])[0] || null;
  const keyCount = (keysState.data?.keys || []).filter(k => !k.revoked_at).length;
  const cap = Number(summary.spending_cap_usd || 0);
  const spend = Number(summary.mtd_estimated_charges_usd || 0);
  const freeCredits = Number(summary.free_credits_remaining_usd || 0);
  const capPct = cap ? Math.min(100, Math.round((spend / cap) * 100)) : 0;
  const loading = overviewState.loading || keysState.loading;
  const primaryAction = keyCount === 0
    ? { href: 'dashboard-api-keys.html', label: 'Create API key' }
    : (freeCredits <= 0
      ? { href: 'dashboard-billing.html', label: 'Set up billing' }
      : { href: 'index.html#playground', label: 'Run Moment Lab' });

  return (
    <DashChrome current="Overview">
      <div style={{ marginBottom: 22, display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16, flexWrap: 'wrap' }}>
        <div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <div style={{ fontFamily: wf.mono, fontSize: 11, letterSpacing: 1, color: 'var(--accent)', textTransform: 'uppercase' }}>dashboard</div>
            <DataBadge live={!!overviewState.data} />
          </div>
          <h1 style={{ fontFamily: wf.sans, fontSize: 30, fontWeight: 600, letterSpacing: -0.4, margin: '6px 0 0' }}>Welcome back, {displayNameFromSession(dashSession())}.</h1>
          <p style={{ fontFamily: wf.sans, fontSize: 14, color: wf.ink3, margin: '6px 0 0' }}>Track credits, jobs, usage, keys, and billing for this MomentIQ account.</p>
        </div>
        <div style={{ display: 'flex', gap: 8 }}>
          <a href={primaryAction.href} style={{ textDecoration: 'none' }}><Btn primary size={13}>{primaryAction.label}</Btn></a>
        </div>
      </div>

      {overviewState.error && <div style={{ ...dashStyles.card, borderColor: '#f0c4c4', color: '#992b2b', marginBottom: 14 }}>Could not load dashboard data: {overviewState.error.message}</div>}

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, minmax(0,1fr))', gap: 12, marginBottom: 14 }}>
        <Stat label="MTD usage" value={loading ? '...' : formatMoney(spend)} sub={`${formatCount(summary.mtd_requests)} requests`} />
        <Stat label="Free credits" value={loading ? '...' : formatMoney(summary.free_credits_remaining_usd)} sub="used before paid billing" />
        <Stat label="Media processed" value={loading ? '...' : `${Number(summary.mtd_minutes_processed || 0).toFixed(1)} min`} sub={`${formatCount(summary.mtd_failed_requests)} failed`} />
        <Stat label="Active keys" value={loading ? '...' : keyCount} sub="revoked keys excluded" />
        <Stat
          label="Worker"
          value={loading ? '...' : (workerHealth.status || 'unknown')}
          sub={workerHealth.last_seen_at ? `${workerHealth.online || 0}/${workerHealth.total || 0} online - ${formatAgeMs(newestWorker?.age_ms)}` : 'no heartbeat yet'}
          accent={workerHealth.status === 'online'}
        />
      </div>

      <AgentSetupPanel keys={keysState.data?.keys || []} />

      <div style={{ display: 'grid', gridTemplateColumns: '1.2fr 0.8fr', gap: 14, alignItems: 'start' }}>
        <div style={dashStyles.card}>
          <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom: 14 }}>
            <div>
              <div style={{ fontFamily: wf.sans, fontSize: 16, fontWeight: 600 }}>Top endpoints by cost</div>
              <div style={{ fontFamily: wf.sans, fontSize: 12.5, color: wf.ink3 }}>Real usage from the active dashboard period.</div>
            </div>
            <a href="dashboard-usage.html" style={{ fontFamily: wf.mono, fontSize: 11, color: 'var(--accent)', textDecoration: 'none' }}>usage detail</a>
          </div>
          <UsageBars data={top} />
        </div>

        <div style={dashStyles.card}>
          <div style={{ fontFamily: wf.sans, fontSize: 16, fontWeight: 600 }}>Spend cap</div>
          <div style={{ fontFamily: wf.sans, fontSize: 12.5, color: wf.ink3, marginTop: 2 }}>{cap ? `${formatMoney(spend)} of ${formatMoney(cap)} used in the current usage period.` : 'No monthly spend cap is set.'}</div>
          <div style={{ height: 9, borderRadius: 999, background: wf.cardAlt, overflow: 'hidden', margin: '14px 0 8px' }}>
            <div style={{ width: `${capPct}%`, height:'100%', background: capPct > 80 ? '#c33' : 'var(--accent)' }} />
          </div>
          <div style={{ display: 'flex', justifyContent:'space-between', fontFamily: wf.mono, fontSize: 11, color: wf.ink3 }}>
            <span>{capPct}% used</span><span>{overviewState.data?.period_label || 'last 31 days'}</span>
          </div>
          <a href="dashboard-billing.html" style={{ display:'inline-block', marginTop: 12, textDecoration:'none' }}><Btn size={13}>Manage billing</Btn></a>
        </div>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14, marginTop: 14 }}>
        <div style={dashStyles.card}>
          <div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', marginBottom: 10 }}>
            <div style={{ fontFamily: wf.sans, fontSize: 16, fontWeight: 600 }}>Recent jobs</div>
            <a href="dashboard-jobs.html" style={{ fontFamily: wf.mono, fontSize: 11, color: 'var(--accent)', textDecoration: 'none' }}>view all</a>
          </div>
          {!recentJobs.length && <div style={{ fontFamily: wf.sans, fontSize: 13, color: wf.ink3 }}>Completed and queued jobs will appear here.</div>}
          {recentJobs.map(r => (
            <div key={r.id} style={{ display:'flex', justifyContent:'space-between', alignItems:'center', gap: 12, padding:'9px 0', borderTop:`1px solid ${wf.line}` }}>
              <div style={{ minWidth: 0 }}>
                <a href={endpointDocHref(r.endpoint)} style={{ fontFamily:wf.mono, fontSize:12, color:wf.ink, textDecoration: 'none' }}>{endpointPath(r.endpoint)}</a>
                <div style={{ fontFamily:wf.sans, fontSize:12, color:wf.ink3, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{mediaName(r.media)} - {formatSeconds(r.duration)}</div>
              </div>
              <span style={{ fontFamily:wf.mono, fontSize:10.5, padding:'2px 7px', borderRadius:999, background:jobOk(r.status)?'#e7f4ec':(r.status==='failed'?'#fceaea':'#fff3d6'), color:jobOk(r.status)?'#1e6b3a':(r.status==='failed'?'#992b2b':'#7a5b00'), textTransform:'uppercase', whiteSpace: 'nowrap' }}>{r.status}</span>
            </div>
          ))}
        </div>

        <div style={dashStyles.card}>
          <div style={{ fontFamily: wf.sans, fontSize: 16, fontWeight: 600, marginBottom: 10 }}>Recent errors</div>
          {!recentErrors.length && <div style={{ fontFamily: wf.sans, fontSize: 13, color: wf.ink3 }}>No recent processing errors.</div>}
          {recentErrors.map((e, i) => (
            <div key={`${e.job_id || e.code}-${i}`} style={{ padding:'9px 0', borderTop:`1px solid ${wf.line}` }}>
              <div style={{ display:'flex', justifyContent:'space-between', gap: 10 }}>
                <span style={{ fontFamily:wf.mono, fontSize:12, color:'#992b2b' }}>{e.http_status || e.code}</span>
                <span style={{ fontFamily:wf.mono, fontSize:11, color:wf.ink3 }}>{e.when || ''}</span>
              </div>
              <div style={{ fontFamily:wf.mono, fontSize:12, marginTop:3 }}>{endpointPath(e.endpoint)}</div>
              <div style={{ fontFamily:wf.sans, fontSize:12.5, color:wf.ink3, marginTop:2 }}>{e.message}</div>
            </div>
          ))}
        </div>
      </div>

      {keyCount === 0 && (
      <div style={{ ...dashStyles.card, marginTop: 14, display: 'flex', justifyContent: 'space-between', gap: 20, alignItems: 'center', flexWrap: 'wrap' }}>
        <div>
          <div style={{ fontFamily: wf.sans, fontSize: 16, fontWeight: 600 }}>Create your first API key</div>
          <div style={{ fontFamily: wf.sans, fontSize: 12.5, color: wf.ink3, marginTop: 3 }}>Your key unlocks API calls, SDK use, and AI-agent integrations.</div>
        </div>
        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
          <a href="dashboard-api-keys.html" style={{ textDecoration:'none' }}><Btn primary size={13}>Create API key</Btn></a>
          <a href="docs-quickstart.html" style={{ textDecoration:'none' }}><Btn size={13}>Quickstart</Btn></a>
        </div>
      </div>
      )}
    </DashChrome>
  );
};

const DashKeys = () => {
  const apiKeysState = useDashApi('/api-keys');
  const connectorsState = useDashApi('/connectors');
  const [creating, setCreating] = React.useState(false);
  const [creatingConnector, setCreatingConnector] = React.useState(false);
  const [createError, setCreateError] = React.useState('');
  const [newKey, setNewKey] = React.useState(null);
  const [newConnector, setNewConnector] = React.useState(null);
  const [copied, setCopied] = React.useState(false);
  const [keyForm, setKeyForm] = React.useState({ name: '', environment: 'test', scope: 'all' });
  const [connectorForm, setConnectorForm] = React.useState({ name: '', connector: 'chatgpt', scope: 'all' });
  const keys = apiKeysState.data?.keys
    ? apiKeysState.data.keys.map(k => ({
        id: k.id,
        name: k.name || safeKeyPrefix(k.prefix) || 'Unnamed key',
        prefix: k.prefix || safeKeyPrefix(k.full_secret_one_time) || '',
        env: k.environment || keyEnvFromPrefix(k.prefix),
        created: k.created_at ? new Date(k.created_at).toLocaleDateString() : '-',
        last: k.last_used_at ? new Date(k.last_used_at).toLocaleString() : 'never',
        usage: formatMoney(k.mtd_usage_usd),
        scope: k.scope || 'all',
        status: k.status || (k.revoked_at ? 'revoked' : 'active'),
      }))
    : [];
  const connectorTokens = connectorsState.data?.connectors
    ? connectorsState.data.connectors.map(t => ({
        id: t.id,
        name: t.name || `${t.connector || 'generic'} connector`,
        connector: t.connector || 'generic',
        prefix: t.prefix || safeKeyPrefix(t.full_secret_one_time) || '',
        scope: t.scope || 'all',
        status: t.status || (t.revoked_at ? 'revoked' : 'active'),
        created: t.created_at ? new Date(t.created_at).toLocaleDateString() : '-',
        last: t.last_used_at ? new Date(t.last_used_at).toLocaleString() : 'never',
      }))
    : [];
  const createKey = async () => {
    setCreateError('');
    const name = keyForm.name.trim();
    if (!name) {
      setCreateError('Name this API key before creating it.');
      return;
    }
    setCreating(true);
    try {
      const payload = await dashApi('/api-keys', {
        method: 'POST',
        body: { name, environment: keyForm.environment, scope: keyForm.scope },
      });
      setNewKey(payload);
      dashTrack('api_key_created', {
        key_environment: payload.key?.environment,
        key_prefix: payload.key?.prefix,
        scope: payload.key?.scope,
      });
    } catch (err) {
      setCreateError(err?.payload?.error?.message || err.message || 'Could not create API key.');
    } finally {
      setCreating(false);
    }
  };
  const copyNewKey = async () => {
    const secret = newKey?.full_secret_one_time;
    if (!secret) return;
    try {
      if (navigator.clipboard?.writeText) {
        await navigator.clipboard.writeText(secret);
      } else {
        const area = document.createElement('textarea');
        area.value = secret;
        area.style.position = 'fixed';
        area.style.opacity = '0';
        document.body.appendChild(area);
        area.select();
        document.execCommand('copy');
        document.body.removeChild(area);
      }
      setCopied(true);
      dashTrack('api_key_copied', {
        key_environment: newKey.key?.environment,
        key_prefix: newKey.key?.prefix,
        location: 'reveal_modal',
      });
    } catch (_err) {
      setCreateError('Could not copy automatically. Select the key and copy it manually.');
    }
  };
  const createConnector = async () => {
    setCreateError('');
    setCreatingConnector(true);
    try {
      const payload = await dashApi('/connectors', {
        method: 'POST',
        body: {
          name: connectorForm.name.trim() || `${connectorForm.connector} connector`,
          connector: connectorForm.connector,
          scope: connectorForm.scope,
        },
      });
      setNewConnector(payload);
      dashTrack('connector_token_created', {
        connector: payload.connector?.connector,
        prefix: payload.connector?.prefix,
        scope: payload.connector?.scope,
      });
    } catch (err) {
      setCreateError(err?.payload?.error?.message || err.message || 'Could not create connector token.');
    } finally {
      setCreatingConnector(false);
    }
  };
  const copyConnector = async () => {
    const secret = newConnector?.full_secret_one_time;
    if (!secret) return;
    try {
      await copyTextToClipboard(secret);
      setCopied(true);
      dashTrack('connector_token_copied', {
        connector: newConnector.connector?.connector,
        prefix: newConnector.connector?.prefix,
      });
    } catch (_err) {
      setCreateError('Could not copy automatically. Select the connector token and copy it manually.');
    }
  };
  const closeConnectorReveal = () => {
    setNewConnector(null);
    setCopied(false);
    window.location.reload();
  };
  const revokeConnector = async (token) => {
    if (!token.id) return;
    try {
      await dashApi(`/connectors/${token.id}/revoke`, { method: 'POST', body: { reason: 'user_requested' } });
      window.location.reload();
    } catch (err) {
      setCreateError(err?.payload?.error?.message || err.message || 'Could not revoke connector token.');
    }
  };
  const closeReveal = () => {
    setNewKey(null);
    setCopied(false);
    window.location.reload();
  };
  const closeKey = async (k) => {
    dashTrack('api_key_close_clicked', { key_environment: k.env, key_prefix: k.prefix, scope: k.scope });
    if (!k.id) return;
    try {
      await dashApi(`/api-keys/${k.id}/close`, { method: 'POST', body: { reason: 'user_requested' } });
      window.location.reload();
    } catch (err) {
      dashTrack('api_key_close_failed', { key_prefix: k.prefix, error_code: err?.payload?.error?.code || 'client_error' });
    }
  };
  return (
    <DashChrome current="API keys">
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom: 22, gap: 12, flexWrap: 'wrap' }}>
        <div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <div style={{ fontFamily: wf.mono, fontSize: 11, letterSpacing: 1, color: 'var(--accent)', textTransform: 'uppercase' }}>api keys</div>
            <DataBadge live={!!apiKeysState.data} />
          </div>
          <h1 style={{ fontFamily: wf.sans, fontSize: 30, fontWeight: 600, letterSpacing: -0.4, margin: '6px 0 0' }}>Create and manage API keys.</h1>
          <p style={{ fontFamily: wf.sans, fontSize: 14, color: wf.ink3, margin: '6px 0 0', maxWidth: 640 }}>
            By default a MomentIQ key works across <code style={{ fontFamily: wf.mono }}>/v1/video/*</code>, <code style={{ fontFamily: wf.mono }}>/v1/audio/*</code>, and <code style={{ fontFamily: wf.mono }}>/v1/timeline/*</code>. Scope, cap, and rotate from here.
          </p>
        </div>
      </div>
      {createError && (
        <div style={{ marginBottom: 14, fontFamily: wf.sans, fontSize: 12.5, color: '#7a1f1f', background: '#fdf6f6', border: '1px solid #f0c4c4', borderRadius: 7, padding: 10 }}>
          {createError}
        </div>
      )}
      {apiKeysState.error && (
        <div style={{ marginBottom: 14, fontFamily: wf.sans, fontSize: 12.5, color: '#7a1f1f', background: '#fdf6f6', border: '1px solid #f0c4c4', borderRadius: 7, padding: 10 }}>
          Could not load API keys: {apiKeysState.error.message}
        </div>
      )}

      <div style={{ ...dashStyles.card, marginBottom: 14, display: 'grid', gridTemplateColumns: '1.4fr 0.7fr 0.8fr auto', gap: 10, alignItems: 'end' }}>
        <label style={{ display: 'grid', gap: 6, fontFamily: wf.sans, fontSize: 12.5, color: wf.ink3 }}>
          Key name
          <input value={keyForm.name} onChange={e => setKeyForm({ ...keyForm, name: e.target.value })} placeholder="e.g. Production app" style={{ border: `1px solid ${wf.line}`, borderRadius: 7, padding: '9px 10px', fontFamily: wf.sans, fontSize: 13, color: wf.ink }} />
        </label>
        <label style={{ display: 'grid', gap: 6, fontFamily: wf.sans, fontSize: 12.5, color: wf.ink3 }}>
          Environment
          <select value={keyForm.environment} onChange={e => setKeyForm({ ...keyForm, environment: e.target.value })} style={{ border: `1px solid ${wf.line}`, borderRadius: 7, padding: '9px 10px', fontFamily: wf.sans, fontSize: 13, color: wf.ink, background: wf.card }}>
            <option value="test">test</option>
            <option value="live">live</option>
          </select>
        </label>
        <label style={{ display: 'grid', gap: 6, fontFamily: wf.sans, fontSize: 12.5, color: wf.ink3 }}>
          Scope
          <select value={keyForm.scope} onChange={e => setKeyForm({ ...keyForm, scope: e.target.value })} style={{ border: `1px solid ${wf.line}`, borderRadius: 7, padding: '9px 10px', fontFamily: wf.sans, fontSize: 13, color: wf.ink, background: wf.card }}>
            <option value="all">all</option>
            <option value="video/*">video/*</option>
            <option value="audio/*">audio/*</option>
            <option value="timeline/*">timeline/*</option>
          </select>
        </label>
        <button type="button" onClick={() => { dashTrack('api_key_create_clicked', { source: 'api_keys_form' }); createKey(); }} disabled={creating} style={{ border: 0, background: 'transparent', padding: 0, cursor: creating ? 'default' : 'pointer' }}>
          <Btn primary size={13}>{creating ? 'Creating...' : 'Create API key'}</Btn>
        </button>
      </div>

      <div style={{...dashStyles.card, padding: 0, overflow:'hidden'}}>
        <table style={{ width:'100%', borderCollapse:'collapse', fontFamily: wf.sans, fontSize: 13 }}>
          <thead><tr style={{ background: wf.cardAlt, color: wf.ink3, textAlign:'left', fontFamily: wf.mono, fontSize: 11, letterSpacing: 1, textTransform: 'uppercase' }}>
            <th style={{ padding: '10px 14px' }}>Name</th>
            <th style={{ padding: '10px 14px' }}>Prefix</th>
            <th style={{ padding: '10px 14px' }}>Env</th>
            <th style={{ padding: '10px 14px' }}>Scope</th>
            <th style={{ padding: '10px 14px' }}>Created</th>
            <th style={{ padding: '10px 14px' }}>Last used</th>
            <th style={{ padding: '10px 14px' }}>Usage MTD</th>
            <th style={{ padding: '10px 14px' }}>Status</th>
            <th style={{ padding: '10px 14px' }}></th>
          </tr></thead>
          <tbody>
            {keys.length === 0 && (
              <tr>
                <td colSpan="9" style={{ padding: '28px 14px', textAlign: 'center', color: wf.ink3, fontFamily: wf.sans }}>
                  No API keys yet. Create one to start testing MomentIQ.
                </td>
              </tr>
            )}
            {keys.map(k => {
              const revoked = k.status === 'revoked';
              return (
                <tr key={k.id || k.name} style={{ borderTop: `1px solid ${wf.line}`, opacity: revoked ? 0.55 : 1 }}>
                  <td style={{ padding: '12px 14px', fontWeight: 500 }}>{k.name}</td>
                  <td style={{ padding: '12px 14px', fontFamily: wf.mono, fontSize: 12, color: wf.ink2 }}>{k.prefix}...</td>
                  <td style={{ padding: '12px 14px' }}><span style={{ fontFamily: wf.mono, fontSize: 10.5, padding: '2px 7px', borderRadius: 999, background: k.env==='live'?'var(--accent-soft)':'#eef0f2', color: k.env==='live'?'var(--accent)':wf.ink3, textTransform: 'uppercase', letterSpacing: 0.6 }}>{k.env}</span></td>
                  <td style={{ padding: '12px 14px', fontFamily: wf.mono, fontSize: 11.5, color: wf.ink3 }}>{k.scope}</td>
                  <td style={{ padding: '12px 14px', color: wf.ink3, fontFamily: wf.mono, fontSize: 11.5 }}>{k.created}</td>
                  <td style={{ padding: '12px 14px', color: wf.ink3 }}>{k.last}</td>
                  <td style={{ padding: '12px 14px', fontFamily: wf.mono }}>{k.usage}</td>
                  <td style={{ padding: '12px 14px' }}>
                    <span style={{
                      fontFamily: wf.mono, fontSize: 10.5, padding: '2px 7px', borderRadius: 999,
                      background: revoked ? '#fceaea' : '#e7f4ec',
                      color: revoked ? '#992b2b' : '#1e6b3a',
                      textTransform: 'uppercase', letterSpacing: 0.6,
                    }}>{k.status}</span>
                  </td>
                  <td style={{ padding: '12px 14px', textAlign: 'right', whiteSpace: 'nowrap' }}>
                    {revoked ? (
                      <span style={{ fontFamily: wf.mono, fontSize: 11, color: wf.ink4 }}>-</span>
                    ) : (
                      <>
                        <span onClick={() => dashTrack('api_key_rotated', { key_environment: k.env, key_prefix: k.prefix, scope: k.scope })}
                              style={{ fontFamily: wf.mono, fontSize: 11, color: wf.ink3, marginRight: 12, cursor: 'pointer' }}>rotate</span>
                        <span onClick={() => closeKey(k)}
                              style={{ fontFamily: wf.mono, fontSize: 11, color: '#c33', cursor: 'pointer' }}>revoke</span>
                      </>
                    )}
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>

      <div id="connectors" style={{ ...dashStyles.card, marginTop: 14, marginBottom: 14 }}>
        <div style={{ display:'flex', justifyContent:'space-between', gap: 16, alignItems:'flex-start', flexWrap: 'wrap', marginBottom: 14 }}>
          <div>
            <div style={{ fontFamily: wf.mono, fontSize: 11, letterSpacing: 1, color: 'var(--accent)', textTransform: 'uppercase' }}>ai connector tokens</div>
            <h2 style={{ fontFamily: wf.sans, fontSize: 21, fontWeight: 600, margin: '5px 0 4px' }}>Connect ChatGPT or Claude without exposing an API key.</h2>
            <div style={{ fontFamily: wf.sans, fontSize: 13, color: wf.ink3, lineHeight: 1.5, maxWidth: 760 }}>
              Connector tokens are for remote MCP clients. They are revocable, shown once, enforce the selected scope, and use the same account credits, billing status, and hard spend cap as normal API calls.
            </div>
          </div>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            <a href="docs-chatgpt.html" style={{ textDecoration: 'none' }}><Btn size={12}>ChatGPT setup</Btn></a>
            <a href="docs-claude.html" style={{ textDecoration: 'none' }}><Btn size={12}>Claude setup</Btn></a>
          </div>
        </div>

        {connectorsState.error && (
          <div style={{ marginBottom: 14, fontFamily: wf.sans, fontSize: 12.5, color: '#7a1f1f', background: '#fdf6f6', border: '1px solid #f0c4c4', borderRadius: 7, padding: 10 }}>
            Could not load connector tokens: {connectorsState.error.message}
          </div>
        )}

        <div style={{ display: 'grid', gridTemplateColumns: '1fr 0.7fr 0.8fr auto', gap: 10, alignItems: 'end', marginBottom: 14 }}>
          <label style={{ display: 'grid', gap: 6, fontFamily: wf.sans, fontSize: 12.5, color: wf.ink3 }}>
            Token name
            <input value={connectorForm.name} onChange={e => setConnectorForm({ ...connectorForm, name: e.target.value })} placeholder="e.g. ChatGPT beta connector" style={{ border: `1px solid ${wf.line}`, borderRadius: 7, padding: '9px 10px', fontFamily: wf.sans, fontSize: 13, color: wf.ink }} />
          </label>
          <label style={{ display: 'grid', gap: 6, fontFamily: wf.sans, fontSize: 12.5, color: wf.ink3 }}>
            Connector
            <select value={connectorForm.connector} onChange={e => setConnectorForm({ ...connectorForm, connector: e.target.value })} style={{ border: `1px solid ${wf.line}`, borderRadius: 7, padding: '9px 10px', fontFamily: wf.sans, fontSize: 13, color: wf.ink, background: wf.card }}>
              <option value="chatgpt">ChatGPT</option>
              <option value="claude">Claude</option>
              <option value="cursor">Cursor</option>
              <option value="replit">Replit</option>
              <option value="lovable">Lovable</option>
              <option value="generic">Generic MCP</option>
            </select>
          </label>
          <label style={{ display: 'grid', gap: 6, fontFamily: wf.sans, fontSize: 12.5, color: wf.ink3 }}>
            Scope
            <select value={connectorForm.scope} onChange={e => setConnectorForm({ ...connectorForm, scope: e.target.value })} style={{ border: `1px solid ${wf.line}`, borderRadius: 7, padding: '9px 10px', fontFamily: wf.sans, fontSize: 13, color: wf.ink, background: wf.card }}>
              <option value="all">all - workflows + guardrails</option>
              <option value="video/*">video/* only</option>
              <option value="audio/*">audio/* only</option>
              <option value="timeline/*">timeline/* only</option>
            </select>
          </label>
          <button type="button" onClick={createConnector} disabled={creatingConnector} style={{ border: 0, background: 'transparent', padding: 0, cursor: creatingConnector ? 'default' : 'pointer' }}>
            <Btn primary size={13}>{creatingConnector ? 'Creating...' : 'Create connector token'}</Btn>
          </button>
        </div>

        <div style={{ overflow: 'hidden', border: `1px solid ${wf.line}`, borderRadius: 8 }}>
          <table style={{ width:'100%', borderCollapse:'collapse', fontFamily: wf.sans, fontSize: 13 }}>
            <thead><tr style={{ background: wf.cardAlt, color: wf.ink3, textAlign:'left', fontFamily: wf.mono, fontSize: 11, letterSpacing: 1, textTransform: 'uppercase' }}>
              <th style={{ padding: '10px 14px' }}>Name</th>
              <th style={{ padding: '10px 14px' }}>Prefix</th>
              <th style={{ padding: '10px 14px' }}>Connector</th>
              <th style={{ padding: '10px 14px' }}>Scope</th>
              <th style={{ padding: '10px 14px' }}>Created</th>
              <th style={{ padding: '10px 14px' }}>Last used</th>
              <th style={{ padding: '10px 14px' }}>Status</th>
              <th style={{ padding: '10px 14px' }}></th>
            </tr></thead>
            <tbody>
              {connectorTokens.length === 0 && (
                <tr>
                  <td colSpan="8" style={{ padding: '22px 14px', textAlign: 'center', color: wf.ink3, fontFamily: wf.sans }}>
                    No connector tokens yet. Create one before connecting ChatGPT or Claude.
                  </td>
                </tr>
              )}
              {connectorTokens.map(token => {
                const revoked = token.status === 'revoked';
                return (
                  <tr key={token.id || token.name} style={{ borderTop: `1px solid ${wf.line}`, opacity: revoked ? 0.55 : 1 }}>
                    <td style={{ padding: '12px 14px', fontWeight: 500 }}>{token.name}</td>
                    <td style={{ padding: '12px 14px', fontFamily: wf.mono, fontSize: 12, color: wf.ink2 }}>{token.prefix}...</td>
                    <td style={{ padding: '12px 14px', fontFamily: wf.mono, fontSize: 11.5, color: wf.ink3 }}>{token.connector}</td>
                    <td style={{ padding: '12px 14px', fontFamily: wf.mono, fontSize: 11.5, color: wf.ink3 }}>{token.scope}</td>
                    <td style={{ padding: '12px 14px', color: wf.ink3, fontFamily: wf.mono, fontSize: 11.5 }}>{token.created}</td>
                    <td style={{ padding: '12px 14px', color: wf.ink3 }}>{token.last}</td>
                    <td style={{ padding: '12px 14px' }}>
                      <span style={{ fontFamily: wf.mono, fontSize: 10.5, padding: '2px 7px', borderRadius: 999, background: revoked ? '#fceaea' : '#e7f4ec', color: revoked ? '#992b2b' : '#1e6b3a', textTransform: 'uppercase', letterSpacing: 0.6 }}>{token.status}</span>
                    </td>
                    <td style={{ padding: '12px 14px', textAlign: 'right' }}>
                      {revoked ? <span style={{ fontFamily: wf.mono, fontSize: 11, color: wf.ink4 }}>-</span> : <span onClick={() => revokeConnector(token)} style={{ fontFamily: wf.mono, fontSize: 11, color: '#c33', cursor: 'pointer' }}>revoke</span>}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>

      <AgentSetupPanel keys={apiKeysState.data?.keys || []} />

      {newConnector && (
      <div style={{ marginTop: 22 }}>
        <div style={{ fontFamily: wf.mono, fontSize: 10.5, letterSpacing: 1, color: wf.ink3, textTransform: 'uppercase', marginBottom: 8 }}>one-time connector token reveal</div>
        <div style={{
          background: '#0d1014', border: `1px solid ${wf.darkLine}`, borderRadius: 12,
          padding: 22, color: wf.card, maxWidth: 680,
          boxShadow: '0 12px 32px rgba(11,13,16,0.18)',
        }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 4 }}>
            <div style={{ fontFamily: wf.sans, fontSize: 16, fontWeight: 600 }}>Your connector token is ready</div>
            <span onClick={closeConnectorReveal} style={{ fontFamily: wf.mono, fontSize: 14, color: wf.darkInk3, cursor: 'pointer' }}>x</span>
          </div>
          <div style={{ fontFamily: wf.sans, fontSize: 12.5, color: wf.darkInk2, lineHeight: 1.55, marginBottom: 12 }}>
            Use this in the ChatGPT or Claude connector auth header. It is shown once and can be revoked any time.
          </div>
          <div style={{ fontFamily: wf.mono, fontSize: 11, color: wf.darkInk3, marginBottom: 6 }}>Authorization: Bearer</div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, background: '#15191e', border: `1px solid ${wf.darkLine}`, borderRadius: 8, padding: '10px 12px' }}>
            <span style={{ fontFamily: wf.mono, fontSize: 13, color: wf.card, flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{newConnector.full_secret_one_time}</span>
            <span style={{ fontFamily: wf.mono, fontSize: 11, padding: '4px 9px', borderRadius: 6, background: 'var(--accent)', color: '#fff', cursor: 'pointer' }} onClick={copyConnector}>{copied ? 'copied' : 'copy'}</span>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 10, fontFamily: wf.mono, fontSize: 11, color: wf.darkInk3 }}>
            <span style={{ width: 6, height: 6, borderRadius: 999, background: '#f5a623' }} />
            One-time reveal - the full connector token will never be shown again.
          </div>
          <div style={{ display: 'flex', gap: 8, marginTop: 16 }}>
            <span onClick={closeConnectorReveal} style={{ fontFamily: wf.sans, fontSize: 13, padding: '8px 12px', borderRadius: 7, background: 'var(--accent)', color: '#fff', fontWeight: 500, cursor: 'pointer' }}>I've copied it</span>
          </div>
        </div>
      </div>
      )}

      {newKey && (
      <div style={{ marginTop: 22 }}>
        <div style={{ fontFamily: wf.mono, fontSize: 10.5, letterSpacing: 1, color: wf.ink3, textTransform: 'uppercase', marginBottom: 8 }}>one-time key reveal</div>
        <div style={{
          background: '#0d1014', border: `1px solid ${wf.darkLine}`, borderRadius: 12,
          padding: 22, color: wf.card, maxWidth: 580,
          boxShadow: '0 12px 32px rgba(11,13,16,0.18)',
        }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 4 }}>
            <div style={{ fontFamily: wf.sans, fontSize: 16, fontWeight: 600 }}>Your new key is ready</div>
            <span onClick={closeReveal} style={{ fontFamily: wf.mono, fontSize: 14, color: wf.darkInk3, cursor: 'pointer' }}>x</span>
          </div>
          <div style={{ fontFamily: wf.sans, fontSize: 12.5, color: wf.darkInk2, lineHeight: 1.55, marginBottom: 12 }}>
            Copy it now - we won't show the full secret again. You can always rotate it later.
          </div>
          <div style={{
            display: 'flex', alignItems: 'center', gap: 10,
            background: '#15191e', border: `1px solid ${wf.darkLine}`, borderRadius: 8, padding: '10px 12px',
          }}>
            <span style={{ fontFamily: wf.mono, fontSize: 13, color: wf.card, flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
              {newKey.full_secret_one_time}
            </span>
            <span style={{ fontFamily: wf.mono, fontSize: 11, padding: '4px 9px', borderRadius: 6, background: 'var(--accent)', color: '#fff', cursor: 'pointer' }}
                  onClick={copyNewKey}>{copied ? 'copied' : 'copy'}</span>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 10, fontFamily: wf.mono, fontSize: 11, color: wf.darkInk3 }}>
            <span style={{ width: 6, height: 6, borderRadius: 999, background: '#f5a623' }} />
            One-time reveal - the full secret will never be shown again.
          </div>
          <div style={{ display: 'flex', gap: 8, marginTop: 16 }}>
            <span onClick={closeReveal} style={{ fontFamily: wf.sans, fontSize: 13, padding: '8px 12px', borderRadius: 7, background: 'var(--accent)', color: '#fff', fontWeight: 500, cursor: 'pointer' }}>I've copied it</span>
          </div>
        </div>
      </div>
      )}

      <div style={{ marginTop: 18, display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 12 }}>
        <div style={dashStyles.card}>
          <div style={{ fontFamily: wf.sans, fontSize: 14.5, fontWeight: 600, marginBottom: 6 }}>Scope a key to one group</div>
          <div style={{ fontFamily: wf.sans, fontSize: 12.5, color: wf.ink3, lineHeight: 1.55 }}>Restrict an agent's key to <code style={{ fontFamily: wf.mono }}>audio/*</code>, <code style={{ fontFamily: wf.mono }}>video/*</code>, or <code style={{ fontFamily: wf.mono }}>timeline/*</code>. Rejected calls return <code style={{ fontFamily: wf.mono }}>403 scope_denied</code>.</div>
        </div>
        <div style={dashStyles.card}>
          <div style={{ fontFamily: wf.sans, fontSize: 14.5, fontWeight: 600, marginBottom: 6 }}>Per-key spending caps</div>
          <div style={{ fontFamily: wf.sans, fontSize: 12.5, color: wf.ink3, lineHeight: 1.55 }}>Give a coding agent a capped key. We refuse calls past the cap with <code style={{ fontFamily: wf.mono }}>402 cap_exceeded</code>.</div>
        </div>
        <div style={{...dashStyles.card, borderColor: '#f0c4c4', background: '#fdf6f6'}}>
          <div style={{ fontFamily: wf.sans, fontSize: 14.5, fontWeight: 600, marginBottom: 6, color: '#7a1f1f' }}>Lost or leaked key?</div>
          <div style={{ fontFamily: wf.sans, fontSize: 12.5, color: '#7a1f1f', lineHeight: 1.55 }}>Revoke closes the key immediately. Servers should reject future calls with <code style={{ fontFamily: wf.mono }}>401 invalid_api_key</code>. Create a replacement key and update your app env vars.</div>
        </div>
      </div>
    </DashChrome>
  );
};

const DashUsage = () => {
  const [period, setPeriod] = React.useState('31d');
  const [groupBy, setGroupBy] = React.useState('endpoint');
  const [query, setQuery] = React.useState('');
  const [sortBy, setSortBy] = React.useState('cost');
  const usageState = useDashApi(`/usage?period=${period}`);
  const endpointRows = (usageState.data?.by_endpoint || []).map(normalizeUsageRow);
  const apiKeyRows = (usageState.data?.by_api_key || []).map(normalizeUsageRow);
  const baseRows = groupBy === 'api_key' ? apiKeyRows : endpointRows;
  const q = query.trim().toLowerCase();
  const rows = baseRows
    .filter(r => !q || `${r.endpoint} ${r.apiKeyName} ${r.apiKeyPrefix} ${r.environment}`.toLowerCase().includes(q))
    .sort((a, b) => {
      if (sortBy === 'requests') return b.requests - a.requests;
      if (sortBy === 'units') return b.billableUnits - a.billableUnits;
      if (sortBy === 'errors') return b.errors - a.errors;
      if (sortBy === 'name') return String(groupBy === 'api_key' ? a.apiKeyName : a.endpoint).localeCompare(String(groupBy === 'api_key' ? b.apiKeyName : b.endpoint));
      return b.cost - a.cost;
    });
  const totals = usageState.data?.totals || {};
  const maxCost = Math.max(1, ...rows.map(r => r.cost));
  const periods = [['7d','7 days'], ['31d','31 days'], ['90d','90 days']];
  return (
    <DashChrome current="Usage">
      <div style={{ marginBottom: 22, display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16, flexWrap: 'wrap' }}>
        <div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <div style={{ fontFamily: wf.mono, fontSize: 11, letterSpacing: 1, color: 'var(--accent)', textTransform: 'uppercase' }}>usage</div>
            <DataBadge live={!!usageState.data} />
          </div>
          <h1 style={{ fontFamily: wf.sans, fontSize: 30, fontWeight: 600, letterSpacing: -0.4, margin: '6px 0 0' }}>Endpoint and API-key usage</h1>
          <p style={{ fontFamily: wf.sans, fontSize: 14, color: wf.ink3, margin: '6px 0 0' }}>Costs, requests, billable units, source media time, and failures from the selected usage period.</p>
        </div>
        <a href="pricing.html" style={{ textDecoration: 'none' }}><Btn size={13}>View pricing</Btn></a>
      </div>
      {usageState.error && <div style={{ ...dashStyles.card, borderColor: '#f0c4c4', color: '#992b2b', marginBottom: 14 }}>Could not load usage: {usageState.error.message}</div>}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, minmax(0,1fr))', gap: 12, marginBottom: 14 }}>
        <Stat label="Total cost" value={usageState.loading ? '...' : formatMoney(totals.cost_usd)} sub={usageState.data?.period_label || 'last 31 days'} />
        <Stat label="Requests" value={usageState.loading ? '...' : formatCount(totals.requests)} sub="completed and failed" />
        <Stat label="Billable units" value={usageState.loading ? '...' : Number(totals.billable_units || totals.media_minutes || 0).toFixed(1)} sub="minutes or requests billed" />
        <Stat label="Source media" value={usageState.loading ? '...' : Number(totals.source_media_minutes || 0).toFixed(1)} sub="raw media minutes touched" />
      </div>
      <div style={dashStyles.card}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom: 12, gap: 12, flexWrap: 'wrap' }}>
          <div>
            <div style={{ fontFamily: wf.sans, fontSize: 16, fontWeight: 600 }}>{groupBy === 'api_key' ? 'Usage by API key' : 'Usage by endpoint'}</div>
            <div style={{ fontFamily: wf.sans, fontSize: 12, color: wf.ink3, marginTop: 2 }}>Billable units reflect the pricing unit for each row. Clip endpoints count clip minutes, not full source length.</div>
          </div>
          <span style={{ fontFamily: wf.mono, fontSize: 11, color: wf.ink3 }}>period: {usageState.data?.period_label || usageState.data?.period || 'last 31 days'}</span>
        </div>
        <div style={{ display: 'flex', gap: 8, marginBottom: 12, flexWrap: 'wrap', alignItems: 'center' }}>
          {periods.map(([value,label]) => <Pill key={value} active={period === value} onClick={() => setPeriod(value)}>{label}</Pill>)}
          <span style={{ width: 1, height: 22, background: wf.line }} />
          <Pill active={groupBy === 'endpoint'} onClick={() => setGroupBy('endpoint')}>By endpoint</Pill>
          <Pill active={groupBy === 'api_key'} onClick={() => setGroupBy('api_key')}>By API key</Pill>
          <span style={{ flex: 1 }} />
          <select value={sortBy} onChange={e=>setSortBy(e.target.value)} style={{ border: `1px solid ${wf.line}`, borderRadius: 7, padding: '7px 9px', fontFamily: wf.sans, fontSize: 12.5, background: wf.card }}>
            <option value="cost">Sort by cost</option>
            <option value="requests">Sort by requests</option>
            <option value="units">Sort by billable units</option>
            <option value="errors">Sort by errors</option>
            <option value="name">Sort by name</option>
          </select>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8, padding: '7px 10px', borderRadius: 7, border: `1px solid ${wf.line}`, background: wf.card, minWidth: 220 }}>
            <input value={query} onChange={e=>setQuery(e.target.value)} placeholder="Filter endpoint or key..." style={{ border: 0, outline: 0, background: 'transparent', flex: 1, fontFamily: wf.sans, fontSize: 12.5, color: wf.ink }} />
          </span>
        </div>
        {!rows.length && <div style={{ fontFamily: wf.sans, fontSize: 13, color: wf.ink3 }}>Usage rows will appear after worker-backed API jobs complete.</div>}
        {!!rows.length && (
          <table style={{ width:'100%', borderCollapse:'collapse', fontFamily: wf.sans, fontSize: 13 }}>
            <thead><tr style={{ color: wf.ink3, textAlign:'left', fontFamily: wf.mono, fontSize: 11, letterSpacing: 1, textTransform: 'uppercase' }}>
              <th style={{ padding: '8px 0' }}>{groupBy === 'api_key' ? 'API key' : 'Endpoint'}</th><th>Rate / env</th><th>Requests</th><th>Billable units</th><th>Source min</th><th>Errors</th><th style={{ textAlign:'right' }}>Cost</th>
            </tr></thead>
            <tbody>
              {rows.map(r => (
                <tr key={groupBy === 'api_key' ? r.apiKeyId : r.endpoint} style={{ borderTop: `1px solid ${wf.line}` }}>
                  <td style={{ padding: '11px 0', fontFamily: wf.mono, fontSize: 12 }}>{groupBy === 'api_key'
                    ? <span>{r.apiKeyName || 'Unknown key'} <span style={{ color: wf.ink3 }}>{r.apiKeyPrefix}</span></span>
                    : <a href={endpointDocHref(r.endpoint)} style={{ color: wf.ink, textDecoration: 'none' }}>{endpointPath(r.endpoint)}</a>}</td>
                  <td style={{ fontFamily: wf.mono, fontSize: 11.5, color: wf.ink3 }}>{groupBy === 'api_key' ? (r.environment || '-') : formatRate(r)}</td>
                  <td>{formatCount(r.requests)}</td>
                  <td>{r.billableUnits.toFixed(2)} {r.unitLabel || (r.pricingUnit === 'request' ? 'requests' : 'min')}</td>
                  <td>{r.sourceMinutes.toFixed(1)}</td>
                  <td>{formatCount(r.errors)}</td>
                  <td style={{ textAlign:'right', fontFamily: wf.mono }}>
                    <span style={{ display:'inline-flex', alignItems:'center', gap:8 }}>
                      <span style={{ width: 80, height: 7, background: wf.cardAlt, borderRadius:999, overflow:'hidden', display:'inline-block' }}><span style={{ display:'block', width:`${Math.max(5, (r.cost / maxCost) * 100)}%`, height:'100%', background:'var(--accent)' }} /></span>
                      {formatMoney(r.cost)}
                    </span>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        )}
      </div>
    </DashChrome>
  );
};

const DashBilling = () => {
  const billingState = useDashApi('/billing');
  const billingData = billingState.data?.billing || {};
  const billingStatus = billingData.billing_status || {};
  const invoices = billingData.invoices || [];
  const alerts = billingData.usage_alerts || [];
  const paymentMethod = billingData.payment_method || null;
  const hasStripeBilling = !!(billingStatus.active || billingStatus.stripe_customer_configured);
  const billingAction = hasStripeBilling
    ? { path: '/billing/customer-portal', kind: 'portal', label: 'Manage billing in Stripe', primary: true }
    : { path: '/billing/checkout-session', kind: 'checkout', label: 'Set up billing', primary: true };
  const startBillingFlow = async (path, kind) => {
    try {
      const data = await dashApi(path, { method: 'POST', body: {} });
      const url = data.checkout?.url || data.portal?.url;
      dashTrack(kind === 'checkout' ? 'billing_checkout_clicked' : 'billing_portal_clicked', { configured: !!url });
      if (url) window.location.href = url;
    } catch (err) {
      alert(err?.payload?.error?.message || err.message || 'Could not start billing flow.');
    }
  };
  return (
    <DashChrome current="Billing">
      <div style={{ marginBottom: 22, display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16, flexWrap: 'wrap' }}>
        <div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <div style={{ fontFamily: wf.mono, fontSize: 11, letterSpacing: 1, color: 'var(--accent)', textTransform: 'uppercase' }}>billing</div>
            <DataBadge live={!!billingState.data} />
          </div>
          <h1 style={{ fontFamily: wf.sans, fontSize: 30, fontWeight: 600, letterSpacing: -0.4, margin: '6px 0 0' }}>Credits, usage, and payment</h1>
          <p style={{ fontFamily: wf.sans, fontSize: 14, color: wf.ink3, margin: '6px 0 0' }}>Free credits are used first. Paid usage starts only after billing is active.</p>
        </div>
        <div style={{ display:'flex', gap:8 }}>
          <Btn primary={billingAction.primary} size={13} onClick={() => startBillingFlow(billingAction.path, billingAction.kind)}>{billingAction.label}</Btn>
        </div>
      </div>
      {billingState.error && <div style={{ ...dashStyles.card, borderColor: '#f0c4c4', color: '#992b2b', marginBottom: 14 }}>Could not load billing: {billingState.error.message}</div>}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, minmax(0,1fr))', gap: 12, marginBottom: 14 }}>
        <Stat label="Free credits" value={billingState.loading ? '...' : formatMoney(billingData.free_credits_remaining_usd)} sub={`${formatMoney(billingData.free_credits_grant_usd)} signup grant`} />
        <Stat label="Recent usage" value={billingState.loading ? '...' : formatMoney(billingData.mtd_usage_usd)} sub={`${billingData.usage_period_label || 'last 31 days'} - ${formatCount(billingData.mtd_requests)} requests`} />
        <Stat label="Spend cap" value={billingState.loading ? '...' : formatMoney(billingData.spending_cap_usd)} sub="monthly hard cap" />
        <Stat label="Billing" value={billingState.loading ? '...' : (billingStatus.active ? 'active' : (billingStatus.status || 'not set'))} sub={billingStatus.configured ? 'Stripe configured' : 'Stripe not configured'} />
      </div>
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:14, marginBottom:14 }}>
        <div style={dashStyles.card}>
          <div style={{ fontFamily: wf.sans, fontSize: 16, fontWeight: 600 }}>Usage alerts</div>
          <div style={{ fontFamily: wf.sans, fontSize: 12.5, color: wf.ink3, marginTop: 2 }}>Alert thresholds configured for this account.</div>
          {!alerts.length && <div style={{ fontFamily: wf.sans, fontSize: 13, color: wf.ink3, marginTop: 12 }}>No usage alerts configured yet.</div>}
          {alerts.map((alert, i) => (
            <div key={`${alert.threshold || i}`} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '8px 0', borderTop: `1px solid ${wf.line}` }}>
              <div>
                <div style={{ fontFamily: wf.sans, fontSize: 13, fontWeight: 500 }}>{alert.label || `${alert.threshold}% cap`}</div>
                <div style={{ fontFamily: wf.sans, fontSize: 11.5, color: wf.ink3 }}>{alert.channel || 'email'}</div>
              </div>
              <span style={{ fontFamily: wf.mono, fontSize: 10.5, padding: '2px 8px', borderRadius: 999, background: alert.enabled === false ? wf.cardAlt : 'var(--accent-soft)', color: alert.enabled === false ? wf.ink3 : 'var(--accent)', textTransform: 'uppercase' }}>{alert.enabled === false ? 'off' : 'on'}</span>
            </div>
          ))}
        </div>
        <div style={dashStyles.card}>
          <div style={{ fontFamily: wf.sans, fontSize: 16, fontWeight: 600 }}>Payment method</div>
          <div style={{ fontFamily: wf.sans, fontSize: 12.5, color: wf.ink3, marginTop: 2 }}>{billingStatus.active ? 'Paid usage is enabled.' : 'Add a card to continue after free credits are used.'}</div>
          <div style={{ marginTop: 14, display:'flex', alignItems:'center', gap:14 }}>
            <div style={{ minWidth: 44, height: 30, borderRadius: 5, background: paymentMethod ? '#0b1d57' : wf.cardAlt, color: paymentMethod ? '#fff' : wf.ink3, fontFamily: wf.sans, fontSize: 10, fontWeight: 600, letterSpacing: 1, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', padding: '0 8px' }}>{paymentMethod?.brand || 'NONE'}</div>
            <div>
              <div style={{ fontFamily: wf.sans, fontSize: 14, fontWeight: 600 }}>{paymentMethod ? `${paymentMethod.brand || 'card'} ending ${paymentMethod.last4}` : 'No payment method yet'}</div>
              <div style={{ fontFamily: wf.sans, fontSize: 12, color: wf.ink3 }}>{paymentMethod?.exp_month ? `Expires ${paymentMethod.exp_month}/${paymentMethod.exp_year}` : 'Stripe Checkout will collect payment details.'}</div>
            </div>
          </div>
        </div>
      </div>
      <div style={dashStyles.card}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', marginBottom: 12 }}>
          <div style={{ fontFamily: wf.sans, fontSize: 16, fontWeight: 600 }}>Billing history</div>
        </div>
        {!invoices.length && <div style={{ fontFamily: wf.sans, fontSize: 13, color: wf.ink3 }}>Invoices will appear here after Stripe creates them.</div>}
        {!!invoices.length && (
          <table style={{ width:'100%', borderCollapse:'collapse', fontFamily: wf.sans, fontSize: 13 }}>
            <thead><tr style={{ color: wf.ink3, textAlign:'left', fontFamily: wf.mono, fontSize: 11, letterSpacing: 1, textTransform: 'uppercase' }}>
              <th style={{ padding: '8px 0' }}>Invoice</th><th>Period</th><th>Amount</th><th>Status</th><th></th>
            </tr></thead>
            <tbody>{invoices.map(inv => (
              <tr key={inv.id} style={{ borderTop: `1px solid ${wf.line}` }}>
                <td style={{ padding: '11px 0', fontFamily: wf.mono, fontSize: 12 }}>{inv.number || inv.id}</td>
                <td>{inv.period || '-'}</td>
                <td style={{ fontFamily: wf.mono }}>{formatMoney(inv.amount_usd)}</td>
                <td><span style={{ fontFamily: wf.mono, fontSize: 10.5, padding: '2px 7px', borderRadius: 999, background: '#e7f4ec', color: '#1e6b3a', textTransform: 'uppercase', letterSpacing: 0.6 }}>{inv.status}</span></td>
                <td style={{ textAlign: 'right' }}>{inv.url && <a href={inv.url} style={{ fontFamily: wf.mono, fontSize: 11, color: wf.ink3, cursor: 'pointer', textDecoration: 'none' }}>open</a>}</td>
              </tr>
            ))}</tbody>
          </table>
        )}
      </div>
    </DashChrome>
  );
};

const DashHistory = () => {
  const [query, setQuery] = React.useState('');
  const [status, setStatus] = React.useState('all');
  const jobsState = useDashApi('/jobs');
  const rows = (jobsState.data?.jobs || []).map(normalizeJob);
  const queryText = query.trim().toLowerCase();
  const visibleRows = rows.filter(r => {
    const isSuccess = jobOk(r.status);
    if (status === 'success' && !isSuccess) return false;
    if (status === 'errors' && r.status !== 'failed') return false;
    if (!queryText) return true;
    return `${r.endpoint} ${r.media} ${r.status} ${r.id}`.toLowerCase().includes(queryText);
  });
  return (
    <DashChrome current="Request log">
      <div style={{ marginBottom: 22, display:'flex', alignItems:'flex-start', justifyContent:'space-between', gap:16, flexWrap:'wrap' }}>
        <div>
          <div style={{ display:'flex', alignItems:'center', gap:10 }}>
            <div style={{ fontFamily: wf.mono, fontSize: 11, letterSpacing: 1, color: 'var(--accent)', textTransform: 'uppercase' }}>request log</div>
            <DataBadge live={!!jobsState.data} />
          </div>
          <h1 style={{ fontFamily: wf.sans, fontSize: 30, fontWeight: 600, letterSpacing: -0.4, margin: '6px 0 0' }}>Request log</h1>
          <p style={{ fontFamily: wf.sans, fontSize: 14, color: wf.ink3, margin: '6px 0 0' }}>A compact audit view of job-backed API activity. Use Jobs for full status, failures, retries, and processing details.</p>
        </div>
      </div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 14 }}>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8, padding: '7px 12px', borderRadius: 8, border: `1px solid ${wf.line}`, background: wf.card, fontFamily: wf.sans, fontSize: 13, color: wf.ink3, flex: 1 }}>
          <svg width="13" height="13" viewBox="0 0 12 12"><circle cx="5" cy="5" r="3.2" fill="none" stroke="currentColor" strokeWidth="1.4"/><line x1="7.5" y1="7.5" x2="10" y2="10" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/></svg>
          <input value={query} onChange={e => setQuery(e.target.value)} placeholder="Filter by endpoint, source, status..." style={{ border: 0, outline: 0, background: 'transparent', flex: 1, fontFamily: wf.sans, fontSize: 13, color: wf.ink }} />
        </span>
        <Pill active={status === 'all'} onClick={() => setStatus('all')}>All</Pill>
        <Pill active={status === 'success'} onClick={() => setStatus('success')}>Success</Pill>
        <Pill active={status === 'errors'} onClick={() => setStatus('errors')}>Errors</Pill>
      </div>
      <div style={{...dashStyles.card, padding: 0, overflow: 'hidden'}}>
        <table style={{ width:'100%', borderCollapse:'collapse', fontFamily: wf.sans, fontSize: 13 }}>
          <thead><tr style={{ background: wf.cardAlt, color: wf.ink3, textAlign:'left', fontFamily: wf.mono, fontSize: 11, letterSpacing: 1, textTransform: 'uppercase' }}>
            <th style={{ padding: '10px 14px' }}>Created</th><th style={{ padding: '10px 14px' }}>Endpoint</th><th style={{ padding: '10px 14px' }}>Source</th><th style={{ padding: '10px 14px' }}>Duration</th><th style={{ padding: '10px 14px' }}>Cost</th><th style={{ padding: '10px 14px' }}>Status</th><th style={{ padding: '10px 14px' }}></th>
          </tr></thead>
          <tbody>
            {visibleRows.map((r) => (
              <tr key={r.id} style={{ borderTop: `1px solid ${wf.line}`, cursor: 'pointer' }}>
                <td style={{ padding: '11px 14px', fontFamily: wf.mono, fontSize: 11.5, color: wf.ink3 }}>{r.created || '-'}</td>
                <td style={{ padding: '11px 14px', fontFamily: wf.mono, fontSize: 12 }}>{endpointPath(r.endpoint)}</td>
                <td style={{ padding: '11px 14px', color: wf.ink3, maxWidth: 240, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{mediaName(r.media)}</td>
                <td style={{ padding: '11px 14px', fontFamily: wf.mono, fontSize: 12, color: wf.ink3 }}>{formatSeconds(r.duration)}</td>
                <td style={{ padding: '11px 14px', fontFamily: wf.mono }}>{r.final == null ? formatMoney(r.estimated) : formatMoney(r.final)}</td>
                <td style={{ padding: '11px 14px' }}><span style={{ fontFamily: wf.mono, fontSize: 10.5, padding: '2px 7px', borderRadius: 999, background: jobOk(r.status)?'#e7f4ec':(r.status==='failed'?'#fceaea':'#fff3d6'), color: jobOk(r.status)?'#1e6b3a':(r.status==='failed'?'#992b2b':'#7a5b00') }}>{r.status}</span></td>
                <td style={{ padding: '11px 14px', textAlign:'right' }}><a href="dashboard-jobs.html" style={{ fontFamily: wf.mono, fontSize: 11, color: 'var(--accent)', textDecoration: 'none' }}>view job</a></td>
              </tr>
            ))}
            {!visibleRows.length && <tr><td colSpan="7" style={{ padding: '18px 14px', color: wf.ink3 }}>{jobsState.loading ? 'Loading requests...' : 'No requests match that filter.'}</td></tr>}
          </tbody>
        </table>
      </div>
    </DashChrome>
  );
};

window.DashOverview = DashOverview;
window.DashKeys = DashKeys;
window.DashUsage = DashUsage;
window.DashBilling = DashBilling;
window.DashHistory = DashHistory;
