// Olympus Analytics dashboard — React via CDN, no build step.
//
// Sections (left nav): Overview · Audience · Acquisition · Behavior ·
//                      Conversions · Realtime · History · Setup
// Top bar: property switcher + date range + token entry.

const { useState, useEffect, useCallback } = React;
const { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Cell } = Recharts;

// ── token store ──────────────────────────────────────────────

const TOKEN_KEY = 'analytics.token';
function getToken() { return localStorage.getItem(TOKEN_KEY) || ''; }
function setToken(t) { if (t) localStorage.setItem(TOKEN_KEY, t); else localStorage.removeItem(TOKEN_KEY); }

async function api(path, opts = {}) {
  const t = getToken();
  const r = await fetch('/api' + path, {
    ...opts,
    headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + t, ...(opts.headers || {}) },
  });
  if (r.status === 401) throw new Error('Unauthorized — set token in Setup tab.');
  if (!r.ok) throw new Error((await r.text()).slice(0, 200));
  return r.json();
}

// ── reusable bits ────────────────────────────────────────────

function FreshBadge({ freshness }) {
  if (!freshness) return null;
  const tier = freshness.from_cache && freshness.tier === 'settled' ? 'cache' : freshness.tier;
  const label = freshness.from_cache ? `${freshness.tier} (cached ${freshness.cache_age_seconds || 0}s)` : freshness.tier;
  return <span className={`px-2 py-0.5 rounded text-xs font-mono freshness-${tier}`}>{label}</span>;
}

function KpiTile({ label, value, fmt }) {
  const v = (typeof value === 'number') ? (fmt ? fmt(value) : value.toLocaleString()) : (value || '—');
  return (
    <div className="card p-4">
      <div className="text-xs uppercase tracking-wide text-gray-500 mb-1">{label}</div>
      <div className="text-2xl font-semibold text-white">{v}</div>
    </div>
  );
}

function fmtPct(n) { return (n * 100).toFixed(1) + '%'; }
function fmtSec(n) { return n.toFixed(1) + 's'; }

// One label format for properties everywhere: domain, stripped of scheme + www.
// GA4 displayNames are inconsistent (some friendly names, some raw domains);
// the domain string is the only thing always present and unambiguous.
function propLabel(p) {
  if (!p) return '';
  if (p.domain) {
    return p.domain.replace(/^https?:\/\//, '').replace(/\/.*$/, '').replace(/^www\./, '');
  }
  return p.name || p.slug || '?';
}

// ── property + date controls ─────────────────────────────────

function TopBar({ properties, property, setProperty, range, setRange }) {
  return (
    <div className="flex items-center justify-between p-4 border-b border-gray-800 bg-[#0c1322]">
      <div className="flex items-center gap-3">
        <div className="text-lg font-semibold text-white">📊 Olympus Analytics</div>
        <select className="bg-gray-800 border border-gray-700 text-white text-sm rounded px-3 py-1.5"
                value={property} onChange={e => setProperty(e.target.value)}>
          {properties.length === 0 && <option value="">(no properties discovered)</option>}
          {properties.map(p => <option key={p.slug} value={p.slug}>{propLabel(p)}{p.disabled ? ' (disabled)' : ''}</option>)}
        </select>
      </div>
      <div className="flex items-center gap-2">
        {['7d','28d','90d','mtd','ytd'].map(r =>
          <button key={r}
                  className={`px-3 py-1.5 text-sm rounded ${range===r ? 'bg-blue-600 text-white' : 'bg-gray-800 text-gray-300 hover:bg-gray-700'}`}
                  onClick={() => setRange(r)}>{r}</button>
        )}
      </div>
    </div>
  );
}

// ── views ────────────────────────────────────────────────────

function OverviewView({ property, range }) {
  const [data, setData] = useState(null);
  const [history, setHistory] = useState([]);
  const [error, setError] = useState(null);

  useEffect(() => {
    if (!property) return;
    setError(null); setData(null);
    api(`/overview?property=${property}&start_date=${range}`)
      .then(setData)
      .catch(e => setError(e.message));
    api(`/historical?property=${property}&metric=users&start=28d&end=today`)
      .then(r => setHistory(r.data.rows || []))
      .catch(() => setHistory([]));
  }, [property, range]);

  if (error) return <div className="p-6 text-red-400">{error}</div>;
  if (!data) return <div className="p-6 text-gray-500">Loading…</div>;
  const k = data.data.kpis;

  return (
    <div className="p-6 space-y-4">
      <div className="flex items-center gap-3">
        <h2 className="text-lg font-semibold text-white">Overview</h2>
        <FreshBadge freshness={data.freshness} />
        <span className="text-xs text-gray-500 ml-auto">{data.data.startDate} → {data.data.endDate}</span>
      </div>
      <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
        <KpiTile label="Users" value={k.users} />
        <KpiTile label="Sessions" value={k.sessions} />
        <KpiTile label="Pageviews" value={k.pageviews} />
        <KpiTile label="Engagement" value={k.engagement_rate} fmt={fmtPct} />
        <KpiTile label="Avg Session" value={k.avg_session_duration} fmt={fmtSec} />
        <KpiTile label="Bounce" value={k.bounce_rate} fmt={fmtPct} />
        <KpiTile label="Key Events" value={k.key_events} />
      </div>

      {history.length > 0 && (
        <div className="card p-4">
          <div className="text-sm text-gray-400 mb-2">Users — last 28 days (from local store)</div>
          <ResponsiveContainer width="100%" height={200}>
            <LineChart data={history}>
              <CartesianGrid stroke="#1f2937" />
              <XAxis dataKey="date" tick={{ fontSize: 10, fill: '#6b7280' }} />
              <YAxis tick={{ fontSize: 10, fill: '#6b7280' }} />
              <Tooltip contentStyle={{ background: '#111827', border: '1px solid #1f2937' }} />
              <Line type="monotone" dataKey="value" stroke="#3b82f6" strokeWidth={2} dot={false} />
            </LineChart>
          </ResponsiveContainer>
        </div>
      )}

      <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
        <div className="card p-4">
          <div className="text-sm text-gray-400 mb-2">Top sources</div>
          <table className="w-full text-sm">
            <tbody>{(data.data.top_sources || []).map((r,i) =>
              <tr key={i} className="border-t border-gray-800">
                <td className="py-1.5 text-gray-300">{r.sessionSourceMedium}</td>
                <td className="py-1.5 text-right font-mono text-white">{r.sessions}</td>
              </tr>
            )}</tbody>
          </table>
        </div>
        <div className="card p-4">
          <div className="text-sm text-gray-400 mb-2">Top pages</div>
          <table className="w-full text-sm">
            <tbody>{(data.data.top_pages || []).map((r,i) =>
              <tr key={i} className="border-t border-gray-800">
                <td className="py-1.5 text-gray-300 truncate max-w-xs">{r.pagePath}</td>
                <td className="py-1.5 text-right font-mono text-white">{r.screenPageViews}</td>
              </tr>
            )}</tbody>
          </table>
        </div>
      </div>
    </div>
  );
}

function GenericTableView({ title, endpoint, query, columns }) {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  useEffect(() => {
    setError(null); setData(null);
    api(endpoint + '?' + new URLSearchParams(query))
      .then(setData).catch(e => setError(e.message));
  }, [endpoint, JSON.stringify(query)]);
  if (error) return <div className="p-6 text-red-400">{error}</div>;
  if (!data) return <div className="p-6 text-gray-500">Loading…</div>;
  const rows = (data.data.rows || data.data.events || []);
  return (
    <div className="p-6 space-y-3">
      <div className="flex items-center gap-3">
        <h2 className="text-lg font-semibold text-white">{title}</h2>
        <FreshBadge freshness={data.freshness} />
      </div>
      <div className="card overflow-x-auto">
        <table className="w-full text-sm">
          <thead><tr className="text-xs uppercase text-gray-500 border-b border-gray-800">
            {columns.map(c => <th key={c.key} className="text-left px-3 py-2">{c.label}</th>)}
          </tr></thead>
          <tbody>{rows.map((r,i) =>
            <tr key={i} className="border-b border-gray-800 hover:bg-gray-900">
              {columns.map(c => <td key={c.key} className="px-3 py-2 font-mono text-white">{c.fmt ? c.fmt(r[c.key]) : (r[c.key] ?? '—')}</td>)}
            </tr>
          )}</tbody>
        </table>
      </div>
    </div>
  );
}

function RealtimeView({ property }) {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const fetchIt = useCallback(() => {
    if (!property) return;
    api('/realtime?property=' + property).then(setData).catch(e => setError(e.message));
  }, [property]);
  useEffect(() => { fetchIt(); const t = setInterval(fetchIt, 30000); return () => clearInterval(t); }, [fetchIt]);
  if (error) return <div className="p-6 text-red-400">{error}</div>;
  if (!data) return <div className="p-6 text-gray-500">Loading…</div>;
  return (
    <div className="p-6 space-y-4">
      <div className="flex items-center gap-3">
        <h2 className="text-lg font-semibold text-white">Realtime</h2>
        <FreshBadge freshness={data.freshness} />
        <span className="text-xs text-gray-500 ml-auto">auto-refresh 30s</span>
      </div>
      <KpiTile label="Active users (last 30 min)" value={data.data.active_users} />
      <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
        <div className="card p-4">
          <div className="text-sm text-gray-400 mb-2">Top active pages</div>
          <table className="w-full text-sm"><tbody>{(data.data.top_pages || []).map((r,i) =>
            <tr key={i} className="border-t border-gray-800">
              <td className="py-1.5 text-gray-300 truncate max-w-xs">{r.unifiedScreenName}</td>
              <td className="py-1.5 text-right font-mono text-white">{r.activeUsers}</td>
            </tr>)}</tbody></table>
        </div>
        <div className="card p-4">
          <div className="text-sm text-gray-400 mb-2">Top countries</div>
          <table className="w-full text-sm"><tbody>{(data.data.top_countries || []).map((r,i) =>
            <tr key={i} className="border-t border-gray-800">
              <td className="py-1.5 text-gray-300">{r.country}</td>
              <td className="py-1.5 text-right font-mono text-white">{r.activeUsers}</td>
            </tr>)}</tbody></table>
        </div>
      </div>
    </div>
  );
}

function HistoryView({ property }) {
  const [metric, setMetric] = useState('users');
  const [data, setData] = useState(null);
  useEffect(() => {
    if (!property) return;
    api(`/historical?property=${property}&metric=${metric}&start=ytd&end=today`)
      .then(setData).catch(() => setData(null));
  }, [property, metric]);
  return (
    <div className="p-6 space-y-3">
      <div className="flex items-center gap-3">
        <h2 className="text-lg font-semibold text-white">History</h2>
        <select className="bg-gray-800 border border-gray-700 text-white text-sm rounded px-2 py-1"
                value={metric} onChange={e => setMetric(e.target.value)}>
          <option value="users">Users</option>
          <option value="sessions">Sessions</option>
          <option value="pageviews">Pageviews</option>
          <option value="engagement_rate">Engagement rate</option>
        </select>
        {data && <FreshBadge freshness={data.freshness} />}
      </div>
      <div className="card p-4">
        {data && data.data.rows.length > 0 ? (
          <ResponsiveContainer width="100%" height={320}>
            <LineChart data={data.data.rows}>
              <CartesianGrid stroke="#1f2937" />
              <XAxis dataKey="date" tick={{ fontSize: 10, fill: '#6b7280' }} />
              <YAxis tick={{ fontSize: 10, fill: '#6b7280' }} />
              <Tooltip contentStyle={{ background: '#111827', border: '1px solid #1f2937' }} />
              <Line type="monotone" dataKey="value" stroke="#3b82f6" strokeWidth={2} dot={false} />
            </LineChart>
          </ResponsiveContainer>
        ) : <div className="text-sm text-gray-500 p-4">No data yet — initial backfill runs on boot.</div>}
      </div>
    </div>
  );
}

function SetupView({ properties, onRefresh }) {
  const [tokenInput, setTokenInput] = useState(getToken());
  const [busy, setBusy] = useState(false);
  async function refresh() { setBusy(true); try { await api('/properties/refresh', { method: 'POST' }); onRefresh(); } finally { setBusy(false); } }
  async function createFilter(slug) { setBusy(true); try { await api(`/filter-create?property=${slug}`, { method: 'POST' }); onRefresh(); } finally { setBusy(false); } }
  return (
    <div className="p-6 space-y-4">
      <h2 className="text-lg font-semibold text-white">Setup</h2>

      <div className="card p-4">
        <div className="text-sm text-gray-400 mb-2">API Token</div>
        <div className="flex gap-2">
          <input className="flex-1 bg-gray-800 border border-gray-700 rounded px-3 py-2 text-sm font-mono"
                 value={tokenInput} onChange={e => setTokenInput(e.target.value)} placeholder="paste ANALYTICS_API_TOKEN" />
          <button className="px-3 py-2 bg-blue-600 hover:bg-blue-500 text-white rounded text-sm" onClick={() => { setToken(tokenInput); location.reload(); }}>Save & reload</button>
        </div>
      </div>

      <div className="card p-4">
        <div className="flex items-center justify-between mb-2">
          <div className="text-sm text-gray-400">Properties (auto-discovered from GA4)</div>
          <button className="px-3 py-1.5 bg-gray-800 hover:bg-gray-700 text-white rounded text-sm" disabled={busy} onClick={refresh}>{busy ? '…' : 'Refresh discovery'}</button>
        </div>
        <table className="w-full text-sm">
          <thead><tr className="text-xs uppercase text-gray-500 border-b border-gray-800">
            <th className="text-left px-2 py-2">Property</th>
            <th className="text-left px-2 py-2">GA4 ID</th>
            <th className="text-left px-2 py-2">Domain</th>
            <th className="text-left px-2 py-2">Internal filter</th>
            <th className="px-2 py-2"></th>
          </tr></thead>
          <tbody>{properties.map(p => {
            const adminUrl = `https://analytics.google.com/analytics/web/#/a${p.ga4_property_id}/admin/streams/table`;
            return (
              <tr key={p.slug} className="border-b border-gray-800">
                <td className="px-2 py-2 text-white">{propLabel(p)}<div className="text-xs text-gray-500">{p.slug}</div></td>
                <td className="px-2 py-2 font-mono text-gray-400">{p.ga4_property_id}</td>
                <td className="px-2 py-2 text-gray-400">{p.domain || '—'}</td>
                <td className="px-2 py-2">
                  <span className="text-gray-400" title="GA4 Admin API doesn't expose internal-traffic-rule state to service accounts">— not API-checkable</span>
                </td>
                <td className="px-2 py-2 space-x-2">
                  <a href={adminUrl} target="_blank" rel="noopener" className="px-2 py-1 bg-blue-700 hover:bg-blue-600 text-white rounded text-xs inline-block">Open in GA4 →</a>
                </td>
              </tr>
            );
          })}</tbody>
        </table>
        <div className="text-xs text-gray-500 mt-3 space-y-2">
          <div className="text-amber-400"><strong>API limitation:</strong> Google's GA4 Admin API does not expose internal-traffic-rule state in any version (v1alpha + v1beta both confirmed). This dashboard cannot verify whether your rules are configured — only you can, in the GA4 web UI.</div>
          <div><strong>Setup (one-time per property, in GA4 web UI):</strong></div>
          <div className="ml-2"><strong>Step 1 — Tag the traffic</strong> (per data stream):</div>
          <div className="ml-4">Open in GA4 → click your <em>web data stream</em> → <strong>Configure tag settings</strong> → <strong>Show all</strong> → <strong>Define internal traffic</strong> → <strong>Create</strong>.</div>
          <div className="ml-4">Set: <strong>traffic_type</strong> = <code className="bg-gray-800 px-1">internal</code>, then add IP-equals conditions for 206.194.219.114, 66.29.128.25, 66.29.128.61. Click <strong>Create</strong> (not "Save and exit").</div>
          <div className="ml-2 mt-1"><strong>Step 2 — Activate the filter</strong> (property level):</div>
          <div className="ml-4">Admin → <strong>Data Filters</strong> → <strong>Internal Traffic</strong> → change state from <em>Testing</em> to <strong>Active</strong>.</div>
          <div className="mt-2"><strong>Self-test:</strong> Visit your site from one of the excluded IPs, then open GA4 → <strong>Realtime</strong>. The visit should not appear (after a few minutes for the rule to apply).</div>
        </div>
      </div>
    </div>
  );
}

// ── shell ────────────────────────────────────────────────────

function App() {
  const [properties, setProperties] = useState([]);
  const [property, setProperty] = useState(null);
  const [range, setRange] = useState('28d');
  const [section, setSection] = useState('overview');
  const [needsToken, setNeedsToken] = useState(!getToken());

  const refresh = useCallback(async () => {
    try {
      const list = await api('/properties');
      setProperties(list);
      if (!property && list.length > 0) {
        const enabled = list.find(p => !p.disabled) || list[0];
        setProperty(enabled.slug);
      }
    } catch (e) { setNeedsToken(true); }
  }, [property]);

  useEffect(() => { if (!needsToken) refresh(); }, [needsToken, refresh]);

  if (needsToken) {
    return (
      <div className="p-10 max-w-xl mx-auto">
        <h1 className="text-xl font-semibold mb-3 text-white">Olympus Analytics — paste API token</h1>
        <p className="text-sm text-gray-400 mb-4">The token was printed at deploy time. Set it once and it's stored in localStorage.</p>
        <SetupView properties={[]} onRefresh={() => { setNeedsToken(false); refresh(); }} />
      </div>
    );
  }

  const navItems = [
    ['overview', 'Overview'], ['audience', 'Audience'], ['acquisition', 'Acquisition'],
    ['behavior', 'Behavior'], ['conversions', 'Conversions'], ['realtime', 'Realtime'],
    ['history', 'History'], ['setup', 'Setup'],
  ];

  return (
    <div className="min-h-screen flex flex-col">
      <TopBar properties={properties} property={property} setProperty={setProperty} range={range} setRange={setRange} />
      <div className="flex flex-1">
        <nav className="w-48 border-r border-gray-800 bg-[#0c1322] py-4">
          {navItems.map(([k, label]) => (
            <button key={k} onClick={() => setSection(k)}
                    className={`w-full text-left px-4 py-2 text-sm ${section===k ? 'bg-blue-600 text-white' : 'text-gray-400 hover:text-white hover:bg-gray-800'}`}>
              {label}
            </button>
          ))}
        </nav>
        <main className="flex-1">
          {!property
            ? <div className="p-6 text-gray-500">No property selected.</div>
            : section === 'overview' ? <OverviewView property={property} range={range} />
            : section === 'audience' ? <GenericTableView title="Audience by country" endpoint="/audience" query={{ property, dimension: 'country', start_date: range }}
                columns={[{key:'country',label:'Country'},{key:'totalUsers',label:'Users'},{key:'sessions',label:'Sessions'},{key:'engagementRate',label:'Engagement',fmt:v=>typeof v==='number'?fmtPct(v):'—'}]} />
            : section === 'acquisition' ? <GenericTableView title="Acquisition by channel" endpoint="/acquisition" query={{ property, breakdown: 'channel', start_date: range }}
                columns={[{key:'sessionDefaultChannelGroup',label:'Channel'},{key:'sessions',label:'Sessions'},{key:'totalUsers',label:'Users'},{key:'engagementRate',label:'Engagement',fmt:v=>typeof v==='number'?fmtPct(v):'—'},{key:'keyEvents',label:'Key Events'}]} />
            : section === 'behavior' ? <GenericTableView title="Top pages" endpoint="/pages" query={{ property, type: 'top', start_date: range, limit: 25 }}
                columns={[{key:'pagePath',label:'Path'},{key:'screenPageViews',label:'Views'},{key:'sessions',label:'Sessions'},{key:'engagementRate',label:'Engagement',fmt:v=>typeof v==='number'?fmtPct(v):'—'}]} />
            : section === 'conversions' ? <GenericTableView title="Conversions (settled only)" endpoint="/conversions" query={{ property, start_date: 'prev_7d', end_date: 'prev_7d' }}
                columns={[{key:'eventName',label:'Event'},{key:'eventCount',label:'Count'},{key:'keyEvents',label:'Key Events'},{key:'totalUsers',label:'Users'}]} />
            : section === 'realtime' ? <RealtimeView property={property} />
            : section === 'history' ? <HistoryView property={property} />
            : section === 'setup' ? <SetupView properties={properties} onRefresh={refresh} />
            : null
          }
        </main>
      </div>
    </div>
  );
}

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