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

// ─── Constants ───────────────────────────────────────────────────────────────
const PLANS = {
  starter: { name: 'Starter', price: 29, color: '#64748b' },
  pro: { name: 'Pro', price: 99, color: '#D4AF37' },
  enterprise: { name: 'Enterprise', price: 499, color: '#a78bfa' },
};

const DEFAULT_COMPANY = {
  name: 'DoneAI',
  address: 'Revenue Operations Center',
  city: 'Monday.com · Live CRM',
  email: 'revops@doneai.ai',
  taxId: null,
};

const EVENT_TYPES = {
  lead: { label: 'New Lead', color: 'text-blue-400', bg: 'bg-blue-500/10' },
  sale: { label: 'Sale', color: 'text-emerald-400', bg: 'bg-emerald-500/10' },
  signup: { label: 'Signup', color: 'text-gold', bg: 'bg-gold/10' },
  churn: { label: 'Churn', color: 'text-red-400', bg: 'bg-red-500/10' },
  renewal: { label: 'Renewal', color: 'text-purple-400', bg: 'bg-purple-500/10' },
  transaction: { label: 'Transaction', color: 'text-slate-300', bg: 'bg-slate-500/10' },
};

const KPI_FORMULAS = {
  mrr: 'Sum of active subscription/pilot amounts per month. When Stripe is connected, uses max(CRM pilot MRR, Stripe active MRR).',
  churn: '(Churned pilots + lost pipeline deals) ÷ total ever subscribed × 100. Weekly series uses churn events in each bucket.',
  ltv: 'ARPU ÷ monthly churn rate. If churn ≈ 0, caps at ARPU × 24 months.',
  pipeline: 'Sum of open pipeline deal values (non-won, non-lost) from Monday Lead Pipeline.',
  arpu: 'MRR ÷ active subscribers/pilots.',
  conversion: 'Active subscribers ÷ (leads + active subscribers) × 100.',
};

const STORAGE_KEYS = {
  settings: 'revops_settings',
  onboarding: 'revops_onboarding',
  audit: 'revops_audit',
  notifRead: 'revops_notif_read',
};

const DEFAULT_SETTINGS = {
  theme: 'dark',
  fontSize: 'md',
  timezone: 'Asia/Dubai',
  role: 'admin',
  thresholds: { largeTx: 50000, newLeads: 1, churnAlert: 1 },
};

const TIMEZONES = [
  { id: 'Asia/Dubai', label: 'Dubai (GST)' },
  { id: 'UTC', label: 'UTC' },
  { id: 'America/New_York', label: 'New York' },
  { id: 'Europe/London', label: 'London' },
  { id: 'Asia/Singapore', label: 'Singapore' },
];

const NAV_ITEMS = [
  { id: 'dashboard', label: 'Dashboard', icon: 'layout' },
  { id: 'revenue', label: 'Revenue', icon: 'trending' },
  { id: 'invoices', label: 'Invoices', icon: 'file' },
  { id: 'receipts', label: 'Receipts', icon: 'receipt' },
  { id: 'feed', label: 'Live Feed', icon: 'radio' },
  { id: 'settings', label: 'Settings', icon: 'settings' },
];

const CHART_RANGES = [
  { id: '30d', label: '30d', days: 30 },
  { id: '90d', label: '90d', days: 90 },
  { id: 'ytd', label: 'YTD', days: null },
  { id: '1y', label: '1y', days: 365 },
];

// ─── Storage ─────────────────────────────────────────────────────────────────
function loadJson(key, fallback) {
  try {
    const raw = localStorage.getItem(key);
    return raw ? JSON.parse(raw) : fallback;
  } catch {
    return fallback;
  }
}

function saveJson(key, value) {
  try { localStorage.setItem(key, JSON.stringify(value)); } catch { /* ignore */ }
}

function loadSettings() {
  return { ...DEFAULT_SETTINGS, ...loadJson(STORAGE_KEYS.settings, {}) };
}

function saveSettings(s) {
  saveJson(STORAGE_KEYS.settings, s);
}

function appendAudit(action, detail) {
  const log = loadJson(STORAGE_KEYS.audit, []);
  log.unshift({ id: Date.now(), action, detail, at: new Date().toISOString() });
  saveJson(STORAGE_KEYS.audit, log.slice(0, 200));
}

function canEditInvoices(role) {
  return role === 'admin';
}

function canEditSettings(role) {
  return role === 'admin';
}

function isSalesReadOnly(role) {
  return role === 'sales';
}

// ─── Utilities ─────────────────────────────────────────────────────────────────
const fmt = (n) => '$' + (Number(n) || 0).toLocaleString('en-US', { maximumFractionDigits: 0 });
const fmtDec = (n) => '$' + (Number(n) || 0).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
const fmtPct = (n) => (Number(n) || 0).toFixed(1) + '%';

const fmtDate = (d, tz) => new Date(d).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric', timeZone: tz });
const fmtTime = (d, tz) => new Date(d).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', timeZone: tz });
const fmtDateTime = (d, tz) => new Date(d).toLocaleString('en-US', { timeZone: tz });

const emptyState = () => ({
  subscribers: [],
  leads: [],
  customers: [],
  transactions: [],
  events: [],
  churned: 0,
  totalEverSubscribed: 0,
  company: DEFAULT_COMPANY,
  meta: null,
  sources: null,
  warnings: [],
  connections: null,
  timeseries: null,
  stripe: null,
});

function connectionDot(status) {
  if (status === 'online') return 'bg-emerald-400';
  if (status === 'degraded' || status === 'empty' || status === 'not_configured') return 'bg-amber-400';
  return 'bg-red-400';
}

function filterSeriesByRange(series, rangeId) {
  if (!series?.length) return [];
  const now = new Date();
  let cutoff;
  if (rangeId === 'ytd') {
    cutoff = new Date(now.getFullYear(), 0, 1).getTime();
  } else {
    const cfg = CHART_RANGES.find((r) => r.id === rangeId);
    const days = cfg?.days || 365;
    cutoff = now.getTime() - days * 86400000;
  }
  return series.filter((p) => new Date(p.date).getTime() >= cutoff);
}

function exportTransactionsCsv(transactions, tz) {
  const headers = ['id', 'date', 'customer', 'company', 'type', 'plan', 'amount', 'salesperson', 'region', 'status'];
  const rows = transactions.map((t) => [
    t.id,
    fmtDateTime(t.timestamp, tz),
    t.customer,
    t.company,
    t.type,
    t.plan,
    t.amount,
    t.salesperson || '',
    t.region || '',
    t.status || '',
  ]);
  const csv = [headers.join(','), ...rows.map((r) => r.map((c) => `"${String(c).replace(/"/g, '""')}"`).join(','))].join('\n');
  const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `revops-transactions-${new Date().toISOString().slice(0, 10)}.csv`;
  a.click();
  URL.revokeObjectURL(url);
}

function buildDrillBreakdown(state, metricKey) {
  const txs = state.transactions || [];
  const subs = (state.subscribers || []).filter((s) => s.status === 'active');
  const byPlan = { starter: 0, pro: 0, enterprise: 0 };
  const byRegion = {};
  const bySales = {};
  const bySegment = { smb: 0, mid: 0, enterprise: 0 };

  const add = (map, key, val) => { map[key] = (map[key] || 0) + val; };

  subs.forEach((s) => {
    const amt = s.amount ?? PLANS[s.plan]?.price ?? 0;
    byPlan[s.plan] = (byPlan[s.plan] || 0) + amt;
    const region = s.region || 'UAE';
    add(byRegion, region, amt);
    add(bySales, s.salesperson || 'Unassigned', amt);
    const seg = s.plan === 'enterprise' ? 'enterprise' : s.plan === 'pro' ? 'mid' : 'smb';
    bySegment[seg] += amt;
  });

  txs.forEach((t) => {
    if (metricKey === 'churn' && t.type !== 'churn') return;
    if (metricKey === 'pipeline' && t.type === 'churn') return;
    const amt = t.amount || 0;
    byPlan[t.plan] = (byPlan[t.plan] || 0) + amt;
    add(byRegion, t.region || 'UAE', amt);
    add(bySales, t.salesperson || 'Unassigned', amt);
    const seg = t.plan === 'enterprise' ? 'enterprise' : t.plan === 'pro' ? 'mid' : 'smb';
    bySegment[seg] += amt;
  });

  if (state.stripe?.connected && state.stripe.plans) {
    Object.entries(state.stripe.plans).forEach(([k, n]) => {
      byPlan[k] = (byPlan[k] || 0) + (n * (PLANS[k]?.price || 0));
    });
  }

  return { byPlan, byRegion, bySales, bySegment };
}

// ─── Icons ───────────────────────────────────────────────────────────────────
const Icon = ({ name, size = 18, className = '' }) => {
  const paths = {
    layout: <><rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/></>,
    trending: <><polyline points="22 7 13.5 15.5 8.5 10.5 2 17"/><polyline points="16 7 22 7 22 13"/></>,
    file: <><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></>,
    receipt: <><path d="M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1Z"/><path d="M16 8h-6a2 2 0 1 0 0 4h4a2 2 0 1 1 0 4H8"/><path d="M12 17.5v-11"/></>,
    radio: <><circle cx="12" cy="12" r="2"/><path d="M16.24 7.76a6 6 0 0 1 0 8.49"/><path d="M7.76 16.24a6 6 0 0 1 0-8.49"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M4.93 19.07a10 10 0 0 1 0-14.14"/></>,
    zap: <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>,
    users: <><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></>,
    dollar: <><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></>,
    x: <><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></>,
    printer: <><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></>,
    chevron: <polyline points="9 18 15 12 9 6"/>,
    activity: <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>,
    arrowUp: <><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></>,
    arrowDown: <><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></>,
    settings: <><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></>,
    bell: <><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></>,
    info: <><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></>,
    sun: <><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></>,
    moon: <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>,
    download: <><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></>,
    mail: <><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></>,
    search: <><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></>,
    filter: <polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/>,
  };
  return (
    <svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 24 24"
      fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
      className={className} aria-hidden="true">
      {paths[name]}
    </svg>
  );
};

// ─── Data hooks ────────────────────────────────────────────────────────────────
function useRevOpsData() {
  const [state, setState] = useState(emptyState);
  const [status, setStatus] = useState('loading');
  const [error, setError] = useState(null);
  const [lastFetched, setLastFetched] = useState(null);

  const load = useCallback(async () => {
    setStatus((s) => (s === 'ready' ? 'refreshing' : 'loading'));
    try {
      const res = await fetch('/api/revops', { cache: 'no-store' });
      const data = await res.json();
      if (!res.ok || !data.ok) {
        setError(data.error || data.hint || `HTTP ${res.status}`);
        setStatus(data.configured === false ? 'unconfigured' : 'error');
        if (data.connections) {
          setState((prev) => ({ ...prev, connections: data.connections, stripe: data.stripe || null }));
        }
        return;
      }
      setError(null);
      setState({
        subscribers: data.subscribers || [],
        leads: data.leads || [],
        customers: data.customers || [],
        transactions: data.transactions || [],
        events: data.events || [],
        churned: data.churned ?? 0,
        totalEverSubscribed: data.totalEverSubscribed ?? 0,
        company: data.company || DEFAULT_COMPANY,
        meta: data.meta || null,
        sources: data.sources || null,
        warnings: data.warnings || [],
        connections: data.connections || null,
        timeseries: data.timeseries || null,
        stripe: data.stripe || null,
      });
      setLastFetched(data.meta?.fetchedAt || new Date().toISOString());
      setStatus('ready');
    } catch (e) {
      setError(e?.message || 'Failed to load RevOps data');
      setStatus('error');
    }
  }, []);

  useEffect(() => {
    load();
    const id = setInterval(load, 60000);
    return () => clearInterval(id);
  }, [load]);

  return { state, status, error, lastFetched, refresh: load };
}

function useMetrics(state) {
  return useMemo(() => {
    const activeSubs = state.subscribers.filter((s) => s.status === 'active');
    const subAmount = (s) => (s.amount != null ? s.amount : PLANS[s.plan]?.price ?? 0);
    let mrr = activeSubs.reduce((sum, s) => sum + subAmount(s), 0);
    const metaMrr = state.meta?.pilotMrr ?? state.meta?.stripeMrr;
    if (metaMrr != null && metaMrr > 0) mrr = Math.max(mrr, metaMrr);
    if (state.stripe?.connected && state.stripe.mrr != null && state.stripe.mrr > 0) {
      mrr = Math.max(mrr, state.stripe.mrr);
    }

    const planCounts = { starter: 0, pro: 0, enterprise: 0 };
    const revenueMix = { starter: 0, pro: 0, enterprise: 0 };
    activeSubs.forEach((s) => {
      if (s.plan) planCounts[s.plan]++;
      revenueMix[s.plan] = (revenueMix[s.plan] || 0) + subAmount(s);
    });

    if (state.stripe?.connected && state.stripe.plans) {
      Object.entries(state.stripe.plans).forEach(([k, n]) => {
        planCounts[k] = (planCounts[k] || 0) + n;
      });
    }

    const pipelineValue = state.meta?.pipelineValue ?? 0;
    const totalRevenue = state.transactions.reduce((s, t) => s + (t.amount || 0), 0);
    const churnRate = state.totalEverSubscribed > 0
      ? (state.churned / state.totalEverSubscribed) * 100
      : 0;
    const arpu = activeSubs.length > 0 ? mrr / activeSubs.length : (state.stripe?.activeCount ? mrr / state.stripe.activeCount : 0);
    const monthlyChurn = Math.max(churnRate / 100, 0.001);
    const ltv = monthlyChurn > 0.001 ? arpu / monthlyChurn : arpu * 24;
    const conversionRate = state.leads.length + activeSubs.length > 0
      ? (activeSubs.length / (state.leads.length + activeSubs.length)) * 100
      : 0;

    return {
      mrr,
      arpu,
      ltv,
      churnRate,
      totalRevenue,
      pipelineValue,
      activeSubs: activeSubs.length || state.stripe?.activeCount || 0,
      planCounts,
      revenueMix,
      conversionRate,
      leadCount: state.leads.length,
      stripeMrr: state.stripe?.mrr,
      stripeConnected: !!state.stripe?.connected,
    };
  }, [state]);
}

function useAppSettings() {
  const [settings, setSettings] = useState(loadSettings);

  useEffect(() => {
    document.documentElement.setAttribute('data-theme', settings.theme);
    document.documentElement.setAttribute('data-font-size', settings.fontSize);
  }, [settings.theme, settings.fontSize]);

  const update = useCallback((patch) => {
    setSettings((prev) => {
      const next = { ...prev, ...patch };
      saveSettings(next);
      return next;
    });
  }, []);

  return { settings, update };
}

// ─── Print helper ──────────────────────────────────────────────────────────────
function openPrintWindow(title, htmlContent) {
  const win = window.open('', '_blank', 'width=800,height=900');
  if (!win) { alert('Please allow pop-ups to print documents.'); return; }
  win.document.write(`<!DOCTYPE html><html><head><title>${title}</title>
    <style>
      body { font-family: 'Helvetica Neue', Arial, sans-serif; color: #1a1a1a; padding: 40px; max-width: 720px; margin: 0 auto; }
      h1 { font-size: 24px; margin: 0 0 4px; }
      .muted { color: #666; font-size: 13px; }
      table { width: 100%; border-collapse: collapse; margin: 24px 0; }
      th, td { padding: 10px 12px; text-align: left; border-bottom: 1px solid #e5e5e5; }
      th { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: #888; }
      .total-row td { border-top: 2px solid #1a1a1a; font-weight: 700; font-size: 16px; }
    </style></head><body>${htmlContent}
    <script>window.onload = function() { window.print(); }<\/script>
    </body></html>`);
  win.document.close();
}

// ─── Connection bar ────────────────────────────────────────────────────────────
const ConnectionStatusBar = ({ connections }) => {
  if (!connections) return null;
  const items = [
    { key: 'monday', label: 'Monday', conn: connections.monday },
    { key: 'stripe', label: 'Stripe', conn: connections.stripe },
    { key: 'timeseries', label: 'Time-series', conn: connections.timeseries },
  ];
  return (
    <div className="flex flex-wrap items-center gap-4 px-4 py-2 border-b theme-divide bg-black/10 text-xs" role="status" aria-label="Data connection status">
      {items.map(({ key, label, conn }) => (
        <div key={key} className="flex items-center gap-2">
          <span className={`w-2 h-2 rounded-full shrink-0 ${connectionDot(conn?.status)}`} aria-hidden="true" />
          <span className="font-medium theme-text-primary">{label}</span>
          <span className="theme-text-secondary capitalize">{conn?.status || 'unknown'}</span>
          {conn?.status === 'online' && conn.mrr != null && (
            <span className="mono text-gold">MRR {fmt(conn.mrr)}</span>
          )}
          {conn?.itemCount != null && <span className="theme-text-secondary">({conn.itemCount} items)</span>}
          {conn?.points != null && <span className="theme-text-secondary">({conn.points} pts)</span>}
          {conn?.error && (
            <span className="text-red-400 max-w-xs truncate" title={conn.error}>{conn.error}</span>
          )}
        </div>
      ))}
    </div>
  );
};

const StatusBadge = ({ status, className = '' }) => {
  const styles = {
    ready: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20',
    loading: 'bg-slate-500/10 text-slate-400 border-slate-500/20',
    refreshing: 'bg-slate-500/10 text-slate-400 border-slate-500/20',
    error: 'bg-red-500/10 text-red-400 border-red-500/20',
    unconfigured: 'bg-amber-500/10 text-amber-400 border-amber-500/20',
  };
  const labels = {
    ready: 'Live',
    loading: 'Loading…',
    refreshing: 'Refreshing…',
    error: 'Error',
    unconfigured: 'Not configured',
  };
  return (
    <span className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[10px] font-semibold uppercase tracking-wider border ${styles[status] || styles.loading} ${className}`}>
      <span className={`w-1.5 h-1.5 rounded-full ${status === 'ready' ? 'bg-emerald-400 live-dot' : 'bg-current opacity-60'}`} />
      {labels[status] || status}
    </span>
  );
};

const DataBanner = ({ status, error, onRetry }) => {
  if (status === 'ready' && !error) return null;
  return (
    <div className={`px-4 py-2 text-center text-xs border-b ${status === 'unconfigured' ? 'bg-amber-500/5 border-amber-500/10 text-amber-200/90' : 'bg-red-500/5 border-red-500/10 text-red-200/90'}`} role="alert">
      {status === 'unconfigured' && (
        <span>Server not configured — set <span className="mono">MONDAY_API_TOKEN</span> or <span className="mono">COMPOSIO_API_KEY</span>.</span>
      )}
      {status === 'error' && (
        <span>{error || 'Could not load live data.'} {onRetry && <button type="button" onClick={onRetry} className="underline ml-1 text-gold">Retry</button>}</span>
      )}
      {status === 'loading' && <span>Loading live RevOps data…</span>}
    </div>
  );
};

// ─── KPI + drill-down ──────────────────────────────────────────────────────────
const InfoTooltip = ({ formula }) => {
  const [open, setOpen] = useState(false);
  return (
    <span className="relative inline-flex ml-1">
      <button type="button" className="text-slate-500 hover:text-gold p-0.5 rounded"
        aria-label="Metric definition" onMouseEnter={() => setOpen(true)} onMouseLeave={() => setOpen(false)}
        onFocus={() => setOpen(true)} onBlur={() => setOpen(false)}>
        <Icon name="info" size={14} />
      </button>
      {open && (
        <span className="absolute z-20 left-0 top-full mt-1 w-56 p-2 text-xs rounded-lg glass text-slate-300 shadow-lg" role="tooltip">
          {formula}
        </span>
      )}
    </span>
  );
};

const KpiCard = ({ label, value, sub, trend, icon, formulaKey, onDrill }) => (
  <button type="button" onClick={onDrill}
    className="glass glass-hover rounded-xl p-5 transition-all duration-300 text-left w-full focus-visible:ring-2 focus-visible:ring-gold"
    aria-label={`${label}: ${value}. Click for breakdown.`}>
    <div className="flex items-start justify-between mb-3">
      <span className="text-xs font-medium theme-text-secondary uppercase tracking-wider flex items-center">
        {label}
        {formulaKey && <InfoTooltip formula={KPI_FORMULAS[formulaKey]} />}
      </span>
      <div className="p-2 rounded-lg bg-gold/5 text-gold">{icon}</div>
    </div>
    <div className="mono text-2xl font-semibold theme-text-primary tracking-tight">{value}</div>
    {sub && (
      <div className="flex items-center gap-1.5 mt-2">
        {trend === 'up' && <Icon name="arrowUp" size={14} className="text-emerald-400" />}
        {trend === 'down' && <Icon name="arrowDown" size={14} className="text-red-400" />}
        <span className="text-xs theme-text-secondary">{sub}</span>
      </div>
    )}
  </button>
);

const DrillDownModal = ({ open, onClose, title, breakdown }) => {
  if (!open) return null;
  const sections = [
    { key: 'byPlan', title: 'By plan', labels: PLANS },
    { key: 'byRegion', title: 'By region' },
    { key: 'bySales', title: 'By salesperson' },
    { key: 'bySegment', title: 'By segment', labels: { smb: 'SMB', mid: 'Mid-market', enterprise: 'Enterprise' } },
  ];
  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm" onClick={onClose} role="dialog" aria-modal="true" aria-labelledby="drill-title">
      <div className="glass rounded-2xl w-full max-w-lg max-h-[85vh] overflow-hidden gold-glow" onClick={(e) => e.stopPropagation()}>
        <div className="flex items-center justify-between p-5 border-b theme-divide">
          <h2 id="drill-title" className="text-lg font-semibold theme-text-primary">{title} breakdown</h2>
          <button type="button" onClick={onClose} className="p-1.5 rounded-lg hover:bg-white/5" aria-label="Close"><Icon name="x" size={18} /></button>
        </div>
        <div className="p-5 overflow-y-auto max-h-[70vh] space-y-6">
          {sections.map(({ key, title: secTitle, labels }) => {
            const data = breakdown[key] || {};
            const entries = Object.entries(data).filter(([, v]) => v > 0).sort((a, b) => b[1] - a[1]);
            if (!entries.length) return null;
            return (
              <div key={key}>
                <h3 className="text-sm font-medium theme-text-secondary mb-2">{secTitle}</h3>
                <ul className="space-y-1.5">
                  {entries.map(([k, v]) => (
                    <li key={k} className="flex justify-between text-sm">
                      <span className="theme-text-secondary">{labels?.[k]?.name || labels?.[k] || k}</span>
                      <span className="mono text-gold">{typeof v === 'number' && v > 100 ? fmt(v) : v}</span>
                    </li>
                  ))}
                </ul>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
};

// ─── SVG trend chart ───────────────────────────────────────────────────────────
const TrendChart = ({ series, rangeId, mode, valuePrefix = '$', height = 180 }) => {
  const filtered = useMemo(() => filterSeriesByRange(series, rangeId), [series, rangeId]);
  const points = useMemo(() => {
    if (!filtered.length) return [];
    return filtered.map((p) => ({
      date: p.date,
      value: mode === 'pct' ? (p.pctChange ?? 0) : (p.value ?? 0),
    }));
  }, [filtered, mode]);

  const w = 600;
  const h = height;
  const pad = { t: 12, r: 12, b: 28, l: 48 };
  const innerW = w - pad.l - pad.r;
  const innerH = h - pad.t - pad.b;

  const { path, area, minV, maxV } = useMemo(() => {
    if (!points.length) return { path: '', area: '', minV: 0, maxV: 1 };
    const vals = points.map((p) => p.value);
    let minV = Math.min(...vals);
    let maxV = Math.max(...vals);
    if (minV === maxV) { minV -= 1; maxV += 1; }
    const range = maxV - minV || 1;
    const coords = points.map((p, i) => {
      const x = pad.l + (i / Math.max(points.length - 1, 1)) * innerW;
      const y = pad.t + innerH - ((p.value - minV) / range) * innerH;
      return [x, y];
    });
    const line = coords.map((c, i) => `${i === 0 ? 'M' : 'L'}${c[0].toFixed(1)},${c[1].toFixed(1)}`).join(' ');
    const areaPath = `${line} L${coords[coords.length - 1][0].toFixed(1)},${pad.t + innerH} L${coords[0][0].toFixed(1)},${pad.t + innerH} Z`;
    return { path: line, area: areaPath, minV, maxV };
  }, [points, innerW, innerH, pad]);

  if (!points.length) {
    return <p className="text-sm theme-text-secondary py-8 text-center">No trend data for this range.</p>;
  }

  return (
    <svg viewBox={`0 0 ${w} ${h}`} className="w-full h-auto" role="img" aria-label="Trend chart">
      <defs>
        <linearGradient id="chartGrad" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor="#D4AF37" stopOpacity="0.35" />
          <stop offset="100%" stopColor="#D4AF37" stopOpacity="0" />
        </linearGradient>
      </defs>
      {[0, 0.5, 1].map((t) => {
        const y = pad.t + innerH * (1 - t);
        const v = minV + (maxV - minV) * t;
        const label = mode === 'pct' ? fmtPct(v) : (valuePrefix === '$' ? fmt(Math.round(v)) : String(Math.round(v)));
        return (
          <g key={t}>
            <line x1={pad.l} y1={y} x2={w - pad.r} y2={y} stroke="rgba(148,163,184,0.15)" strokeWidth="1" />
            <text x={pad.l - 6} y={y + 4} textAnchor="end" fill="#94a3b8" fontSize="10" fontFamily="JetBrains Mono">{label}</text>
          </g>
        );
      })}
      <path d={area} fill="url(#chartGrad)" />
      <path d={path} fill="none" stroke="#D4AF37" strokeWidth="2" strokeLinejoin="round" />
      {points.length > 0 && (
        <>
          <text x={pad.l} y={h - 6} fill="#64748b" fontSize="9">{points[0].date}</text>
          <text x={w - pad.r} y={h - 6} textAnchor="end" fill="#64748b" fontSize="9">{points[points.length - 1].date}</text>
        </>
      )}
    </svg>
  );
};

const ChartPanel = ({ title, series, valuePrefix }) => {
  const [rangeId, setRangeId] = useState('90d');
  const [mode, setMode] = useState('abs');
  return (
    <div className="glass rounded-xl p-5">
      <div className="flex flex-wrap items-center justify-between gap-2 mb-4">
        <h3 className="text-sm font-medium theme-text-primary">{title}</h3>
        <div className="flex flex-wrap gap-1">
          {CHART_RANGES.map((r) => (
            <button key={r.id} type="button" onClick={() => setRangeId(r.id)}
              className={`px-2 py-0.5 rounded text-xs ${rangeId === r.id ? 'bg-gold/20 text-gold' : 'theme-text-secondary hover:bg-white/5'}`}
              aria-pressed={rangeId === r.id}>{r.label}</button>
          ))}
          <button type="button" onClick={() => setMode('abs')} className={`px-2 py-0.5 rounded text-xs ml-1 ${mode === 'abs' ? 'bg-gold/20 text-gold' : 'theme-text-secondary'}`} aria-pressed={mode === 'abs'}>Abs</button>
          <button type="button" onClick={() => setMode('pct')} className={`px-2 py-0.5 rounded text-xs ${mode === 'pct' ? 'bg-gold/20 text-gold' : 'theme-text-secondary'}`} aria-pressed={mode === 'pct'}>%</button>
        </div>
      </div>
      <TrendChart series={series} rangeId={rangeId} mode={mode} valuePrefix={valuePrefix} />
    </div>
  );
};

// ─── Filterable list ─────────────────────────────────────────────────────────
function useListFilters(items, { searchKeys = ['customer', 'company'], dateKey = 'timestamp' }) {
  const [search, setSearch] = useState('');
  const [sortKey, setSortKey] = useState('date');
  const [sortDir, setSortDir] = useState('desc');
  const [typeFilter, setTypeFilter] = useState('all');
  const [statusFilter, setStatusFilter] = useState('all');
  const [dateFrom, setDateFrom] = useState('');
  const [dateTo, setDateTo] = useState('');

  const filtered = useMemo(() => {
    let list = [...items];
    const q = search.trim().toLowerCase();
    if (q) {
      list = list.filter((item) => searchKeys.some((k) => String(item[k] || '').toLowerCase().includes(q)));
    }
    if (typeFilter !== 'all') list = list.filter((i) => i.type === typeFilter);
    if (statusFilter !== 'all') list = list.filter((i) => (i.status || '').toLowerCase().includes(statusFilter.toLowerCase()));
    if (dateFrom) list = list.filter((i) => i[dateKey] >= new Date(dateFrom).getTime());
    if (dateTo) list = list.filter((i) => i[dateKey] <= new Date(dateTo).getTime() + 86400000);
    list.sort((a, b) => {
      let cmp = 0;
      if (sortKey === 'date') cmp = (a[dateKey] || 0) - (b[dateKey] || 0);
      else if (sortKey === 'amount') cmp = (a.amount || 0) - (b.amount || 0);
      else cmp = String(a.customer || a.message || '').localeCompare(String(b.customer || b.message || ''));
      return sortDir === 'asc' ? cmp : -cmp;
    });
    return list;
  }, [items, search, sortKey, sortDir, typeFilter, statusFilter, dateFrom, dateTo, searchKeys, dateKey]);

  return { filtered, search, setSearch, sortKey, setSortKey, sortDir, setSortDir, typeFilter, setTypeFilter, statusFilter, setStatusFilter, dateFrom, setDateFrom, dateTo, setDateTo };
}

const ListToolbar = ({ filters, types, showStatus }) => (
  <div className="p-4 border-b theme-divide space-y-3">
    <div className="flex flex-wrap gap-2 items-center">
      <div className="flex-1 min-w-[180px] relative">
        <Icon name="search" size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-500" />
        <input type="search" placeholder="Search…" value={filters.search} onChange={(e) => filters.setSearch(e.target.value)}
          className="w-full pl-9 pr-3 py-2 rounded-lg bg-black/20 border theme-divide text-sm theme-text-primary"
          aria-label="Search list" />
      </div>
      <select value={filters.sortKey} onChange={(e) => filters.setSortKey(e.target.value)} className="px-2 py-2 rounded-lg bg-black/20 border theme-divide text-sm" aria-label="Sort by">
        <option value="date">Date</option>
        <option value="amount">Amount</option>
        <option value="name">Name</option>
      </select>
      <button type="button" onClick={() => filters.setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'))} className="px-2 py-2 rounded-lg bg-black/20 text-sm" aria-label="Toggle sort direction">
        {filters.sortDir === 'asc' ? '↑' : '↓'}
      </button>
      {types && (
        <select value={filters.typeFilter} onChange={(e) => filters.setTypeFilter(e.target.value)} className="px-2 py-2 rounded-lg bg-black/20 border theme-divide text-sm" aria-label="Filter by type">
          <option value="all">All types</option>
          {types.map((t) => <option key={t} value={t}>{t}</option>)}
        </select>
      )}
      {showStatus && (
        <input type="text" placeholder="Status filter" value={filters.statusFilter === 'all' ? '' : filters.statusFilter}
          onChange={(e) => filters.setStatusFilter(e.target.value || 'all')} className="px-2 py-2 rounded-lg bg-black/20 border theme-divide text-sm w-32" aria-label="Filter by status" />
      )}
      <input type="date" value={filters.dateFrom} onChange={(e) => filters.setDateFrom(e.target.value)} className="px-2 py-2 rounded-lg bg-black/20 text-sm" aria-label="From date" />
      <input type="date" value={filters.dateTo} onChange={(e) => filters.setDateTo(e.target.value)} className="px-2 py-2 rounded-lg bg-black/20 text-sm" aria-label="To date" />
    </div>
  </div>
);

// ─── Onboarding ────────────────────────────────────────────────────────────────
const OnboardingPanel = ({ onDismiss }) => (
  <div className="glass rounded-xl p-5 mb-6 border border-gold/20" role="region" aria-label="Getting started">
    <div className="flex justify-between items-start gap-4">
      <div>
        <h3 className="text-sm font-semibold text-gold mb-2">Getting started</h3>
        <ul className="text-sm theme-text-secondary space-y-1 list-disc list-inside">
          <li>Connect Monday via <span className="mono">MONDAY_API_TOKEN</span> or Composio</li>
          <li>Link Stripe for billing MRR: <span className="mono">composio link stripe</span></li>
          <li>Click KPI cards for plan / region / sales breakdowns</li>
          <li>Use Settings for timezone, alerts, role, and theme</li>
        </ul>
      </div>
      <button type="button" onClick={onDismiss} className="text-xs text-slate-500 hover:text-gold shrink-0" aria-label="Dismiss onboarding">Dismiss</button>
    </div>
  </div>
);

// ─── Notifications ─────────────────────────────────────────────────────────────
function buildNotifications(state, metrics, thresholds) {
  const alerts = [];
  const txThreshold = thresholds.largeTx || 50000;
  (state.transactions || []).forEach((t) => {
    if ((t.amount || 0) >= txThreshold) {
      alerts.push({ id: `tx-${t.id}`, type: 'transaction', message: `Large transaction: ${t.customer} ${fmtDec(t.amount)}`, at: t.timestamp });
    }
    if (t.type === 'churn') {
      alerts.push({ id: `churn-${t.id}`, type: 'churn', message: `Churn: ${t.customer}`, at: t.timestamp });
    }
  });
  (state.leads || []).slice(0, thresholds.newLeads || 5).forEach((l) => {
    alerts.push({ id: `lead-${l.id}`, type: 'lead', message: `New lead: ${l.name} @ ${l.company}`, at: l.created });
  });
  alerts.sort((a, b) => (b.at || 0) - (a.at || 0));
  return alerts.slice(0, 30);
}

const NotificationsBell = ({ alerts, tz, onMarkRead }) => {
  const [open, setOpen] = useState(false);
  const unread = alerts.length;
  return (
    <div className="relative">
      <button type="button" onClick={() => setOpen(!open)} className="p-2 rounded-lg hover:bg-white/5 relative" aria-label={`Notifications, ${unread} alerts`} aria-expanded={open}>
        <Icon name="bell" size={18} />
        {unread > 0 && <span className="absolute top-1 right-1 w-2 h-2 bg-red-500 rounded-full" aria-hidden="true" />}
      </button>
      {open && (
        <div className="absolute right-0 mt-2 w-80 max-h-96 overflow-y-auto glass rounded-xl border theme-divide shadow-xl z-30">
          <div className="p-3 border-b theme-divide flex justify-between items-center">
            <span className="text-sm font-medium">Alerts</span>
            <button type="button" className="text-xs text-gold" onClick={() => { onMarkRead(); setOpen(false); }}>Mark read</button>
          </div>
          {alerts.length === 0 ? (
            <p className="p-4 text-sm theme-text-secondary">No alerts</p>
          ) : alerts.map((a) => (
            <div key={a.id} className="px-3 py-2 border-b theme-divide text-sm">
              <span className={`text-[10px] uppercase font-semibold ${EVENT_TYPES[a.type]?.color || 'text-slate-400'}`}>{a.type}</span>
              <p className="theme-text-primary mt-0.5">{a.message}</p>
              <p className="text-xs theme-text-secondary mono mt-0.5">{fmtDateTime(a.at, tz)}</p>
            </div>
          ))}
        </div>
      )}
    </div>
  );
};

// ─── Progress bar ──────────────────────────────────────────────────────────────
const ProgressBar = ({ label, value, max, color = 'bg-gold', pct }) => {
  const width = max > 0 ? Math.min((value / max) * 100, 100) : 0;
  return (
    <div className="mb-4">
      <div className="flex justify-between text-xs mb-1.5">
        <span className="theme-text-secondary">{label}</span>
        <span className="mono theme-text-primary">{pct || fmt(value)}</span>
      </div>
      <div className="h-2 bg-slate-800 rounded-full overflow-hidden">
        <div className={`bar-fill h-full rounded-full ${color}`} style={{ width: `${width}%` }} />
      </div>
    </div>
  );
};

// ─── Modals ────────────────────────────────────────────────────────────────────
function InvoiceModal({ open, onClose, transactions, company, canEdit }) {
  if (!open) return null;
  const recent = transactions.slice(0, 8);
  const invoiceNo = 'INV-' + Date.now().toString(36).toUpperCase();
  const subtotal = recent.reduce((s, t) => s + (t.amount || 0), 0);

  const handlePrint = () => {
    appendAudit('invoice_print', invoiceNo);
    const rows = recent.map((t) => `<tr><td>${fmtDate(t.timestamp)}</td><td>${t.customer}</td><td style="text-align:right">${fmtDec(t.amount)}</td></tr>`).join('');
    openPrintWindow(`Invoice ${invoiceNo}`, `
      <div class="header"><h1>${company.name}</h1><p class="muted">${invoiceNo}</p></div>
      <table><thead><tr><th>Date</th><th>Description</th><th>Amount</th></tr></thead><tbody>${rows}
      <tr class="total-row"><td colspan="2">Total</td><td style="text-align:right">${fmtDec(subtotal)}</td></tr></tbody></table>`);
  };

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm" onClick={onClose} role="dialog" aria-modal="true">
      <div className="glass rounded-2xl w-full max-w-2xl max-h-[85vh] overflow-hidden gold-glow print-area" onClick={(e) => e.stopPropagation()}>
        <div className="flex items-center justify-between p-5 border-b theme-divide">
          <h2 className="text-lg font-semibold theme-text-primary">Invoice Generator</h2>
          <div className="flex gap-2">
            {canEdit && (
              <button type="button" onClick={handlePrint} className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-gold/10 text-gold text-sm" aria-label="Print invoice">
                <Icon name="printer" size={16} /> Print / PDF
              </button>
            )}
            <button type="button" onClick={onClose} aria-label="Close"><Icon name="x" size={18} /></button>
          </div>
        </div>
        {!canEdit && <p className="px-5 py-2 text-xs text-amber-400">Read-only: your role cannot generate invoices.</p>}
        <div className="p-5 overflow-y-auto max-h-[60vh]">
          <table className="w-full text-sm">
            <thead><tr className="text-xs theme-text-secondary uppercase border-b theme-divide">
              <th className="pb-2 text-left">Date</th><th className="pb-2 text-left">Description</th><th className="pb-2 text-right">Amount</th>
            </tr></thead>
            <tbody>
              {recent.map((t) => (
                <tr key={t.id} className="border-b theme-divide">
                  <td className="py-2.5 text-xs">{fmtDate(t.timestamp)}</td>
                  <td className="py-2.5">{t.customer}</td>
                  <td className="py-2.5 text-right mono text-gold">{fmtDec(t.amount)}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  );
}

function ReceiptModal({ open, onClose, transaction, company, canEdit }) {
  if (!open || !transaction) return null;
  const receiptNo = 'RCT-' + String(transaction.id).toUpperCase();

  const handlePrint = () => {
    appendAudit('receipt_print', receiptNo);
    openPrintWindow(`Receipt ${receiptNo}`, `
      <h1>${company.name}</h1>
      <p>Receipt ${receiptNo} · ${fmtDec(transaction.amount)} · ${transaction.customer}</p>`);
  };

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm" onClick={onClose} role="dialog" aria-modal="true">
      <div className="glass rounded-2xl w-full max-w-md gold-glow print-area" onClick={(e) => e.stopPropagation()}>
        <div className="flex items-center justify-between p-5 border-b theme-divide">
          <h2 className="text-lg font-semibold theme-text-primary">Receipt</h2>
          <div className="flex gap-2">
            {canEdit && (
              <button type="button" onClick={handlePrint} className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-gold/10 text-gold text-sm">
                <Icon name="printer" size={16} /> Print / PDF
              </button>
            )}
            <button type="button" onClick={onClose} aria-label="Close"><Icon name="x" size={18} /></button>
          </div>
        </div>
        <div className="p-6 text-center">
          <div className="mono text-3xl font-bold theme-text-primary">{fmtDec(transaction.amount)}</div>
          <div className="text-sm theme-text-secondary mt-2">{transaction.customer}</div>
        </div>
      </div>
    </div>
  );
}

// ─── Pages ─────────────────────────────────────────────────────────────────────
function DashboardView({ metrics, state, dataStatus, timeseries, onDrill, showOnboarding, onDismissOnboarding }) {
  const maxMix = Math.max(...Object.values(metrics.revenueMix), 1);
  const maxPlan = Math.max(...Object.values(metrics.planCounts), 1);
  const funnelMax = Math.max(metrics.leadCount, metrics.activeSubs, 1);
  const ts = timeseries || {};

  return (
    <div className="space-y-6">
      <div className="flex items-center justify-between">
        <div>
          <h1 className="text-xl font-semibold theme-text-primary">Command Dashboard</h1>
          <p className="text-sm theme-text-secondary mt-0.5">Live metrics · Monday CRM{metrics.stripeConnected ? ' + Stripe' : ''}</p>
        </div>
        <StatusBadge status={dataStatus} />
      </div>

      {showOnboarding && <OnboardingPanel onDismiss={onDismissOnboarding} />}

      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
        <KpiCard label="MRR" value={fmt(metrics.mrr)} sub={`${metrics.activeSubs} active${metrics.stripeConnected ? ' · Stripe merged' : ''}`}
          trend="up" icon={<Icon name="dollar" size={18} />} formulaKey="mrr" onDrill={() => onDrill('mrr')} />
        <KpiCard label="Churn Rate" value={fmtPct(metrics.churnRate)} sub={`${state.churned} churned`}
          trend={metrics.churnRate > 5 ? 'down' : 'up'} icon={<Icon name="activity" size={18} />} formulaKey="churn" onDrill={() => onDrill('churn')} />
        <KpiCard label="LTV (est.)" value={fmt(Math.round(metrics.ltv))} sub={`ARPU ${fmt(Math.round(metrics.arpu))}`}
          trend="up" icon={<Icon name="trending" size={18} />} formulaKey="ltv" onDrill={() => onDrill('ltv')} />
        <KpiCard label="Open Pipeline" value={fmt(metrics.pipelineValue)} sub={`${state.meta?.openPipelineDeals ?? '—'} deals`}
          icon={<Icon name="zap" size={18} />} formulaKey="pipeline" onDrill={() => onDrill('pipeline')} />
      </div>

      {ts.mrr?.length > 0 && (
        <div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
          <ChartPanel title="MRR trend" series={ts.mrr} valuePrefix="$" />
          <ChartPanel title="Churn %" series={ts.churn} valuePrefix="%" />
          <ChartPanel title="LTV trend" series={ts.ltv} valuePrefix="$" />
        </div>
      )}

      <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
        <div className="glass rounded-xl p-5">
          <h3 className="text-sm font-medium theme-text-primary mb-4">Revenue Mix by Plan</h3>
          {Object.entries(PLANS).map(([key, plan]) => (
            <ProgressBar key={key} label={plan.name} value={metrics.revenueMix[key] || 0} max={maxMix}
              color={key === 'pro' ? 'bg-gold' : key === 'enterprise' ? 'bg-purple-500' : 'bg-slate-500'} pct={fmt(metrics.revenueMix[key] || 0)} />
          ))}
        </div>
        <div className="glass rounded-xl p-5">
          <h3 className="text-sm font-medium theme-text-primary mb-4">Subscriber Distribution</h3>
          {Object.entries(PLANS).map(([key, plan]) => (
            <ProgressBar key={key} label={plan.name} value={metrics.planCounts[key] || 0} max={maxPlan}
              color={key === 'pro' ? 'bg-gold' : key === 'enterprise' ? 'bg-purple-500' : 'bg-slate-500'}
              pct={`${metrics.planCounts[key] || 0} subs`} />
          ))}
        </div>
      </div>
    </div>
  );
}

function RevenueView({ state, metrics, dataStatus, lastFetched, tz, onExportCsv, onEmailReport }) {
  const txFilters = useListFilters(state.transactions);
  const types = [...new Set(state.transactions.map((t) => t.type))];

  return (
    <div className="space-y-6">
      <div className="flex flex-wrap items-center justify-between gap-3">
        <div>
          <h1 className="text-xl font-semibold theme-text-primary">Revenue</h1>
          <p className="text-sm theme-text-secondary mt-0.5">Timestamps converted to {tz}</p>
        </div>
        <div className="flex items-center gap-2 flex-wrap">
          <StatusBadge status={dataStatus} />
          {lastFetched && <span className="text-xs theme-text-secondary mono">Updated {fmtDateTime(lastFetched, tz)}</span>}
          <button type="button" onClick={onExportCsv} className="flex items-center gap-1 px-3 py-1.5 rounded-lg bg-gold/10 text-gold text-xs" aria-label="Export CSV">
            <Icon name="download" size={14} /> CSV
          </button>
          <button type="button" onClick={onEmailReport} className="flex items-center gap-1 px-3 py-1.5 rounded-lg bg-white/5 text-sm text-xs" aria-label="Email report">
            <Icon name="mail" size={14} /> Email
          </button>
        </div>
      </div>

      <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
        <div className="glass rounded-xl p-5">
          <div className="text-xs theme-text-secondary uppercase mb-2">Customers</div>
          <div className="mono text-3xl font-bold theme-text-primary">{state.customers.length}</div>
        </div>
        <div className="glass rounded-xl p-5">
          <div className="text-xs theme-text-secondary uppercase mb-2">Active Leads</div>
          <div className="mono text-3xl font-bold text-blue-400">{state.leads.length}</div>
        </div>
        <div className="glass rounded-xl p-5">
          <div className="text-xs theme-text-secondary uppercase mb-2">MRR by tier</div>
          <div className="space-y-1 mt-2">
            {Object.entries(PLANS).map(([k, p]) => (
              <div key={k} className="flex justify-between text-sm">
                <span className="theme-text-secondary">{p.name}</span>
                <span className="mono text-gold">{fmt(metrics.revenueMix[k] || 0)}</span>
              </div>
            ))}
          </div>
        </div>
      </div>

      <div className="glass rounded-xl overflow-hidden">
        <div className="p-4 border-b theme-divide flex justify-between items-center">
          <h3 className="text-sm font-medium theme-text-primary">Recent Transactions</h3>
        </div>
        <ListToolbar filters={txFilters} types={types} showStatus />
        <div className="divide-y theme-divide max-h-96 overflow-y-auto">
          {txFilters.filtered.slice(0, 50).map((t) => (
            <div key={t.id} className="flex items-center justify-between px-4 py-3 hover:bg-white/[0.02]">
              <div className="flex items-center gap-3">
                <div className={`w-8 h-8 rounded-lg flex items-center justify-center text-xs capitalize ${EVENT_TYPES[t.type]?.bg || 'bg-slate-500/10'}`}>
                  {(t.type || '?')[0].toUpperCase()}
                </div>
                <div>
                  <div className="text-sm theme-text-primary">{t.customer}</div>
                  <div className="text-xs theme-text-secondary">{t.company} · {(PLANS[t.plan] || {}).name || t.plan}</div>
                </div>
              </div>
              <div className="text-right">
                <div className="mono text-sm text-gold">{fmtDec(t.amount)}</div>
                <div className="text-xs theme-text-secondary" title={new Date(t.timestamp).toISOString()}>{fmtTime(t.timestamp, tz)}</div>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function InvoicesView({ transactions, onOpenInvoice, canEdit }) {
  return (
    <div className="space-y-6">
      <h1 className="text-xl font-semibold theme-text-primary">Invoices</h1>
      <div className="glass rounded-xl p-8 text-center">
        <Icon name="file" size={32} className="text-gold mx-auto mb-4" />
        <p className="text-sm theme-text-secondary mb-6">Printable invoice from recent CRM activity.</p>
        <button type="button" onClick={onOpenInvoice} disabled={!canEdit}
          className={`px-6 py-2.5 rounded-lg font-semibold text-sm ${canEdit ? 'bg-gold text-dark hover:bg-gold-dim' : 'bg-slate-700 text-slate-400 cursor-not-allowed'}`}
          aria-label="Generate invoice">
          Generate Invoice
        </button>
        {!canEdit && <p className="text-xs text-amber-400 mt-3">Finance/Sales roles: invoices are read-only.</p>}
      </div>
    </div>
  );
}

function ReceiptsView({ transactions, onOpenReceipt, canEdit, tz }) {
  const [selected, setSelected] = useState(null);
  const filters = useListFilters(transactions);
  const types = [...new Set(transactions.map((t) => t.type))];

  return (
    <div className="space-y-6">
      <h1 className="text-xl font-semibold theme-text-primary">Receipts</h1>
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
        <div className="glass rounded-xl overflow-hidden">
          <ListToolbar filters={filters} types={types} />
          <div className="divide-y theme-divide max-h-80 overflow-y-auto">
            {filters.filtered.slice(0, 20).map((t) => (
              <button key={t.id} type="button" onClick={() => setSelected(t)}
                className={`w-full flex items-center justify-between px-4 py-3 text-left hover:bg-white/[0.02] ${selected?.id === t.id ? 'bg-gold/5 border-l-2 border-gold' : ''}`}>
                <div>
                  <div className="text-sm theme-text-primary">{t.customer}</div>
                  <div className="text-xs theme-text-secondary">{fmtTime(t.timestamp, tz)}</div>
                </div>
                <span className="mono text-sm text-gold">{fmtDec(t.amount)}</span>
              </button>
            ))}
          </div>
        </div>
        <div className="glass rounded-xl p-6 flex flex-col items-center justify-center min-h-[280px]">
          {selected ? (
            <>
              <div className="mono text-2xl font-bold theme-text-primary">{fmtDec(selected.amount)}</div>
              <button type="button" onClick={() => onOpenReceipt(selected)} disabled={!canEdit}
                className={`mt-4 px-5 py-2 rounded-lg text-sm ${canEdit ? 'bg-gold/10 text-gold' : 'bg-slate-700 text-slate-400'}`}>
                Print Receipt
              </button>
            </>
          ) : (
            <p className="text-sm theme-text-secondary">Select a transaction</p>
          )}
        </div>
      </div>
    </div>
  );
}

function FeedView({ events, dataStatus, tz }) {
  const items = events.map((e) => ({ ...e, customer: e.message, type: e.type, timestamp: e.timestamp, amount: 0 }));
  const filters = useListFilters(items, { searchKeys: ['message'], dateKey: 'timestamp' });
  const types = [...new Set(events.map((e) => e.type))];

  return (
    <div className="space-y-6">
      <div className="flex items-center justify-between">
        <div>
          <h1 className="text-xl font-semibold theme-text-primary">Live Feed</h1>
          <p className="text-sm theme-text-secondary">Converted to {tz} · raw ISO in title</p>
        </div>
        <StatusBadge status={dataStatus} />
      </div>
      <div className="glass rounded-xl overflow-hidden">
        <ListToolbar filters={filters} types={types} />
        <div className="divide-y theme-divide max-h-[calc(100vh-320px)] overflow-y-auto">
          {filters.filtered.length === 0 ? (
            <div className="p-8 text-center theme-text-secondary text-sm">No events</div>
          ) : filters.filtered.map((ev, i) => {
            const meta = EVENT_TYPES[ev.type] || EVENT_TYPES.transaction;
            return (
              <div key={ev.id} className={`flex items-start gap-3 px-4 py-3 feed-enter ${i === 0 ? 'bg-gold/[0.03]' : ''}`}>
                <div className={`px-2 py-0.5 rounded text-[10px] font-semibold uppercase ${meta.bg} ${meta.color}`}>{meta.label}</div>
                <div className="flex-1 min-w-0">
                  <p className="text-sm theme-text-primary">{ev.message}</p>
                  <p className="text-xs theme-text-secondary mt-0.5 mono" title={new Date(ev.timestamp).toISOString()}>{fmtDateTime(ev.timestamp, tz)}</p>
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

function SettingsView({ settings, update, auditLog }) {
  const canEdit = canEditSettings(settings.role);
  return (
    <div className="space-y-6 max-w-2xl">
      <h1 className="text-xl font-semibold theme-text-primary">Settings</h1>

      <section className="glass rounded-xl p-5 space-y-4">
        <h2 className="text-sm font-medium text-gold">Appearance & accessibility</h2>
        <div className="flex flex-wrap gap-3 items-center">
          <span className="text-sm theme-text-secondary">Theme</span>
          <button type="button" onClick={() => update({ theme: 'dark' })} className={`px-3 py-1.5 rounded-lg text-sm flex items-center gap-1 ${settings.theme === 'dark' ? 'bg-gold/20 text-gold' : ''}`} aria-pressed={settings.theme === 'dark'}>
            <Icon name="moon" size={14} /> Dark
          </button>
          <button type="button" onClick={() => update({ theme: 'light' })} className={`px-3 py-1.5 rounded-lg text-sm flex items-center gap-1 ${settings.theme === 'light' ? 'bg-gold/20 text-gold' : ''}`} aria-pressed={settings.theme === 'light'}>
            <Icon name="sun" size={14} /> Light
          </button>
        </div>
        <div className="flex flex-wrap gap-2 items-center">
          <span className="text-sm theme-text-secondary w-full">Font size</span>
          {['sm', 'md', 'lg'].map((fs) => (
            <button key={fs} type="button" onClick={() => update({ fontSize: fs })}
              className={`px-3 py-1.5 rounded-lg text-sm uppercase ${settings.fontSize === fs ? 'bg-gold/20 text-gold' : ''}`} aria-pressed={settings.fontSize === fs}>{fs}</button>
          ))}
        </div>
        <label className="block text-sm theme-text-secondary">
          Timezone
          <select value={settings.timezone} onChange={(e) => update({ timezone: e.target.value })} className="mt-1 w-full px-3 py-2 rounded-lg bg-black/20 border theme-divide" disabled={!canEdit}>
            {TIMEZONES.map((tz) => <option key={tz.id} value={tz.id}>{tz.label}</option>)}
          </select>
        </label>
      </section>

      <section className="glass rounded-xl p-5 space-y-4">
        <h2 className="text-sm font-medium text-gold">Role (RBAC)</h2>
        <select value={settings.role} onChange={(e) => canEdit && update({ role: e.target.value })} disabled={!canEdit}
          className="w-full px-3 py-2 rounded-lg bg-black/20 border theme-divide text-sm" aria-label="User role">
          <option value="admin">Admin — full access</option>
          <option value="finance">Finance — no invoice edit</option>
          <option value="sales">Sales — read-only invoices</option>
        </select>
      </section>

      <section className="glass rounded-xl p-5 space-y-3">
        <h2 className="text-sm font-medium text-gold">Notification thresholds</h2>
        <label className="text-sm theme-text-secondary block">Large transaction ($)
          <input type="number" value={settings.thresholds?.largeTx ?? 50000} disabled={!canEdit}
            onChange={(e) => update({ thresholds: { ...settings.thresholds, largeTx: Number(e.target.value) } })}
            className="mt-1 w-full px-3 py-2 rounded-lg bg-black/20 border theme-divide mono" />
        </label>
      </section>

      <section className="glass rounded-xl p-5">
        <h2 className="text-sm font-medium text-gold mb-3">Audit log</h2>
        <ul className="text-xs space-y-2 max-h-48 overflow-y-auto mono theme-text-secondary">
          {(auditLog || []).length === 0 ? <li>No actions logged</li> : auditLog.map((e) => (
            <li key={e.id}>{e.at} · {e.action}: {e.detail}</li>
          ))}
        </ul>
      </section>
    </div>
  );
}

// ─── Root App ──────────────────────────────────────────────────────────────────
function App() {
  const { state, status, error, lastFetched, refresh } = useRevOpsData();
  const { settings, update } = useAppSettings();
  const [page, setPage] = useState('dashboard');
  const [invoiceOpen, setInvoiceOpen] = useState(false);
  const [receiptOpen, setReceiptOpen] = useState(false);
  const [receiptTx, setReceiptTx] = useState(null);
  const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
  const [drill, setDrill] = useState(null);
  const [showOnboarding, setShowOnboarding] = useState(() => !loadJson(STORAGE_KEYS.onboarding, {}).dismissed);
  const [auditLog, setAuditLog] = useState(() => loadJson(STORAGE_KEYS.audit, []));

  const metrics = useMetrics(state);
  const company = state.company || DEFAULT_COMPANY;
  const tz = settings.timezone;
  const role = settings.role;
  const canInvoice = canEditInvoices(role) && !isSalesReadOnly(role);
  const canReceipt = role === 'admin' || role === 'sales';

  const alerts = useMemo(() => buildNotifications(state, metrics, settings.thresholds), [state, metrics, settings.thresholds]);

  const dismissOnboarding = () => {
    saveJson(STORAGE_KEYS.onboarding, { dismissed: true });
    setShowOnboarding(false);
  };

  const handleDrill = (key) => setDrill({ key, breakdown: buildDrillBreakdown(state, key) });

  const handleExportCsv = () => {
    exportTransactionsCsv(state.transactions, tz);
    appendAudit('export_csv', `${state.transactions.length} rows`);
    setAuditLog(loadJson(STORAGE_KEYS.audit, []));
  };

  const handleEmailReport = () => {
    const body = encodeURIComponent(
      `RevOps Summary\nMRR: ${fmt(metrics.mrr)}\nChurn: ${fmtPct(metrics.churnRate)}\nLTV: ${fmt(Math.round(metrics.ltv))}\nPipeline: ${fmt(metrics.pipelineValue)}\nLeads: ${state.leads.length}\nFetched: ${lastFetched || 'n/a'}`
    );
    window.location.href = `mailto:${company.email}?subject=${encodeURIComponent('RevOps Report')}&body=${body}`;
    appendAudit('email_report', 'mailto');
    setAuditLog(loadJson(STORAGE_KEYS.audit, []));
  };

  const openReceipt = useCallback((tx) => {
    setReceiptTx(tx);
    setReceiptOpen(true);
  }, []);

  const renderPage = () => {
    switch (page) {
      case 'dashboard':
        return <DashboardView metrics={metrics} state={state} dataStatus={status} timeseries={state.timeseries}
          onDrill={handleDrill} showOnboarding={showOnboarding} onDismissOnboarding={dismissOnboarding} />;
      case 'revenue':
        return <RevenueView state={state} metrics={metrics} dataStatus={status} lastFetched={lastFetched} tz={tz}
          onExportCsv={handleExportCsv} onEmailReport={handleEmailReport} />;
      case 'invoices':
        return <InvoicesView transactions={state.transactions} onOpenInvoice={() => setInvoiceOpen(true)} canEdit={canInvoice} />;
      case 'receipts':
        return <ReceiptsView transactions={state.transactions} onOpenReceipt={openReceipt} canEdit={canReceipt} tz={tz} />;
      case 'feed':
        return <FeedView events={state.events} dataStatus={status} tz={tz} />;
      case 'settings':
        return <SettingsView settings={settings} update={update} auditLog={auditLog} />;
      default:
        return null;
    }
  };

  return (
    <div className="min-h-screen flex flex-col">
      <ConnectionStatusBar connections={state.connections} />
      <DataBanner status={status} error={error} onRetry={refresh} />

      <div className="flex flex-1">
        <aside className={`${sidebarCollapsed ? 'w-16' : 'w-56'} glass border-r theme-divide flex flex-col transition-all shrink-0`} aria-label="Main navigation">
          <div className="p-4 border-b theme-divide">
            <div className="flex items-center gap-2.5">
              <div className="w-8 h-8 rounded-lg bg-gold/10 flex items-center justify-center shrink-0">
                <Icon name="zap" size={18} className="text-gold" />
              </div>
              {!sidebarCollapsed && (
                <div>
                  <div className="text-sm font-bold theme-text-primary">DoneAI</div>
                  <div className="text-[10px] theme-text-secondary uppercase tracking-widest">RevOps</div>
                </div>
              )}
            </div>
          </div>
          <nav className="flex-1 p-2 space-y-0.5">
            {NAV_ITEMS.map((item) => (
              <button key={item.id} type="button" onClick={() => setPage(item.id)}
                className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm transition-all
                  ${page === item.id ? 'bg-gold/10 text-gold border border-gold/20' : 'theme-text-secondary hover:bg-white/[0.03] border border-transparent'}`}
                aria-current={page === item.id ? 'page' : undefined}>
                <Icon name={item.icon} size={18} />
                {!sidebarCollapsed && <span>{item.label}</span>}
              </button>
            ))}
          </nav>
          <div className="p-2 border-t theme-divide">
            <button type="button" onClick={() => setSidebarCollapsed((c) => !c)} className="w-full p-2 rounded-lg theme-text-secondary hover:bg-white/[0.03]" aria-label={sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}>
              <Icon name="chevron" size={16} className={sidebarCollapsed ? '' : 'rotate-180'} />
            </button>
          </div>
        </aside>

        <main className="flex-1 overflow-y-auto">
          <header className="sticky top-0 z-10 glass border-b theme-divide px-6 py-3 flex flex-wrap items-center justify-between gap-3">
            <div className="flex items-center gap-3 flex-wrap">
              <StatusBadge status={status} />
              <span className="text-xs theme-text-secondary mono">MRR {fmt(metrics.mrr)} · {metrics.activeSubs} active</span>
            </div>
            <div className="flex items-center gap-2 flex-wrap">
              <label className="text-xs theme-text-secondary flex items-center gap-1">
                <span className="sr-only">Timezone</span>
                <select value={tz} onChange={(e) => update({ timezone: e.target.value })} className="px-2 py-1 rounded bg-black/20 border theme-divide text-xs" aria-label="Display timezone">
                  {TIMEZONES.map((t) => <option key={t.id} value={t.id}>{t.label}</option>)}
                </select>
              </label>
              <NotificationsBell alerts={alerts} tz={tz} onMarkRead={() => saveJson(STORAGE_KEYS.notifRead, Date.now())} />
              <button type="button" onClick={refresh} className="text-xs theme-text-secondary hover:text-gold">Refresh</button>
            </div>
          </header>
          <div className="p-6 max-w-7xl mx-auto">{renderPage()}</div>
        </main>
      </div>

      <footer className="border-t theme-divide px-6 py-3 text-center">
        <span className="text-[11px] theme-text-secondary">DoneAI RevOps · Monday + Stripe + computed trends</span>
      </footer>

      <InvoiceModal open={invoiceOpen} onClose={() => setInvoiceOpen(false)} transactions={state.transactions} company={company} canEdit={canInvoice} />
      <ReceiptModal open={receiptOpen} onClose={() => setReceiptOpen(false)} transaction={receiptTx} company={company} canEdit={canReceipt} />
      <DrillDownModal open={!!drill} onClose={() => setDrill(null)} title={drill?.key || ''} breakdown={drill?.breakdown || {}} />
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
