// G-INTEL — Live Frontend (Cloudflare Pages)
// Real API-connected UI. Uses the same HUD design tokens as the prototype.

const { useState, useEffect, useMemo, useCallback, useRef } = React;
const CFG = window.GINTEL_CONFIG;

// ─── API client ───────────────────────────────────────────
const api = {
  base: () => CFG.API_BASE || '',
  headers(withAuth) {
    const h = { 'content-type': 'application/json' };
    if (withAuth && CFG.ADMIN_TOKEN) h.authorization = `Bearer ${CFG.ADMIN_TOKEN}`;
    return h;
  },
  async get(path) {
    const r = await fetch(this.base() + path, { credentials: 'include' });
    if (!r.ok) throw new Error(`GET ${path} → ${r.status}`);
    return r.json();
  },
  async send(method, path, body, withAuth = true) {
    const r = await fetch(this.base() + path, {
      method,
      headers: this.headers(withAuth),
      credentials: 'include',
      body: body ? JSON.stringify(body) : undefined,
    });
    if (!r.ok) {
      let err = `${method} ${path} → ${r.status}`;
      try { const j = await r.json(); if (j?.error) err += ` · ${j.error}`; } catch {}
      throw new Error(err);
    }
    return r.json().catch(() => ({}));
  },
};

// ─── Utils ────────────────────────────────────────────────
const fmtAgo = (ms) => {
  if (!ms) return '—';
  const d = Date.now() - ms;
  if (d < 0) return 'now';
  if (d < 60_000) return `${Math.floor(d/1000)}秒前`;
  if (d < 3_600_000) return `${Math.floor(d/60_000)}分前`;
  if (d < 86_400_000) return `${Math.floor(d/3_600_000)}時間前`;
  return `${Math.floor(d/86_400_000)}日前`;
};

const StatusChip = ({ status }) => {
  const s = CFG.STATUS[status] ?? CFG.STATUS.unknown;
  return (
    <span className="mono" style={{
      fontSize: 9, fontWeight: 700, letterSpacing: '0.12em',
      color: s.color, padding: '2px 6px',
      border: `1px solid ${s.color}`, background: `${s.color}18`,
    }}>{s.label}</span>
  );
};

const CatChip = ({ category }) => {
  const c = CFG.CATEGORIES[category] ?? CFG.CATEGORIES.custom;
  return (
    <span className="mono" style={{
      fontSize: 8, fontWeight: 700, letterSpacing: '0.14em',
      color: c.color, padding: '2px 5px', border: `1px solid ${c.color}66`,
    }}>{c.label}</span>
  );
};

// ─── Top bar ──────────────────────────────────────────────
function TopBar({ health, tab, onTab, mode }) {
  const tabs = ['EVENTS', 'STORES', 'CONFIG'];
  return (
    <div style={{
      position: 'sticky', top: 0, zIndex: 20,
      background: 'linear-gradient(180deg, rgba(5,7,12,0.98), rgba(5,7,12,0.9))',
      borderBottom: '1px solid var(--line)', backdropFilter: 'blur(8px)',
    }}>
      <div style={{
        maxWidth: 1200, margin: '0 auto', padding: '10px 18px',
        display: 'flex', alignItems: 'center', gap: 18,
      }}>
        <div style={{
          width: 34, height: 34, background: 'var(--rx-red)',
          display: 'grid', placeItems: 'center',
          fontFamily: 'var(--f-display)', fontSize: 18, fontWeight: 900, color: '#fff',
          boxShadow: '0 0 12px rgba(230,57,70,0.5)',
        }}>G</div>
        <div style={{ display: 'flex', flexDirection: 'column' }}>
          <span className="mono" style={{ fontSize: 9, color: 'var(--rx-yellow)', letterSpacing: '0.22em', fontWeight: 700 }}>
            G-INTEL · RESTOCK INTEL TERMINAL
          </span>
          <span className="disp" style={{ fontSize: 15, fontWeight: 800, letterSpacing: '0.08em', color: '#f5f5f7' }}>
            GUNPLA INTELLIGENCE
          </span>
        </div>

        <div style={{ flex: 1 }} />

        <div style={{ display: 'flex', gap: 4 }}>
          {tabs.map(t => (
            <button key={t} onClick={() => onTab(t)}
              className="mono"
              style={{
                background: t === tab ? 'var(--rx-yellow)' : 'transparent',
                color: t === tab ? '#05070c' : 'var(--text-dim)',
                border: `1px solid ${t === tab ? 'var(--rx-yellow)' : 'var(--line)'}`,
                padding: '7px 14px', fontSize: 10, fontWeight: 800, letterSpacing: '0.18em',
                cursor: 'pointer',
              }}>{t}</button>
          ))}
        </div>

        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginLeft: 8 }}>
          <span className={`hud-pulse ${health ? '' : 'red'}`} />
          <span className="mono" style={{ fontSize: 9, color: 'var(--text-dim)', letterSpacing: '0.15em' }}>
            {health ? `ONLINE · ${mode?.toUpperCase() ?? '—'}` : 'OFFLINE'}
          </span>
        </div>
      </div>
    </div>
  );
}

// ─── Events tab ───────────────────────────────────────────
function EventsTab({ events, stores, loading, onRefresh, filter, setFilter }) {
  const byId = useMemo(() => Object.fromEntries(stores.map(s => [s.id, s])), [stores]);
  return (
    <div style={{ maxWidth: 1200, margin: '0 auto', padding: '16px 18px' }}>
      {/* Filter bar */}
      <div style={{
        display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap',
        padding: 12, border: '1px solid var(--line)', background: 'var(--bg-panel)', marginBottom: 14,
      }} className="hud-corners">
        <span className="hud-c1" /><span className="hud-c2" />
        <span className="mono" style={{ fontSize: 9, color: 'var(--rx-yellow)', letterSpacing: '0.2em', fontWeight: 700 }}>▸ FILTER</span>
        <select value={filter.storeId} onChange={e => setFilter(f => ({ ...f, storeId: e.target.value }))}
          className="mono" style={selectStyle}>
          <option value="">ALL STORES</option>
          {stores.map(s => <option key={s.id} value={s.id}>{s.code} · {s.name}</option>)}
        </select>
        <select value={filter.status} onChange={e => setFilter(f => ({ ...f, status: e.target.value }))}
          className="mono" style={selectStyle}>
          <option value="">ALL STATUS</option>
          {Object.entries(CFG.STATUS).map(([k, v]) => <option key={k} value={k}>{v.label}</option>)}
        </select>
        <span style={{ flex: 1 }} />
        <button onClick={onRefresh} disabled={loading} className="mono" style={btnPrimary}>
          {loading ? '· FETCHING ·' : '↻ REFRESH'}
        </button>
      </div>

      {/* Event feed */}
      <div style={{ display: 'grid', gap: 8 }}>
        {events.length === 0 && !loading && (
          <div style={emptyBox}>
            <div className="mono" style={{ fontSize: 10, color: 'var(--text-mute)', letterSpacing: '0.2em' }}>
              NO EVENTS YET · Cron runs every minute — check back soon
            </div>
          </div>
        )}
        {events.map(e => <EventRow key={e.id} ev={e} store={byId[e.store_id]} />)}
      </div>
    </div>
  );
}

function EventRow({ ev, store }) {
  const color = ev.store_color || '#8b95a7';
  return (
    <div style={{
      display: 'grid', gridTemplateColumns: '48px 1fr auto',
      gap: 12, padding: 12, background: 'var(--bg-panel)',
      border: '1px solid var(--line)', alignItems: 'center',
    }}>
      <div style={{
        width: 44, height: 44, display: 'grid', placeItems: 'center',
        border: `1px solid ${color}`, color, fontFamily: 'var(--f-display)',
        fontWeight: 900, fontSize: 14, letterSpacing: '0.06em',
      }}>{ev.store_code}</div>

      <div>
        <div className="jp" style={{ fontSize: 15, fontWeight: 600, color: '#f5f5f7', marginBottom: 3 }}>
          {ev.product_name}
        </div>
        <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' }}>
          <StatusChip status={ev.status} />
          {ev.qty_text && (
            <span className="mono" style={{ fontSize: 10, color: 'var(--text-dim)' }}>{ev.qty_text}</span>
          )}
          {ev.price && (
            <span className="mono" style={{ fontSize: 10, color: 'var(--rx-yellow)' }}>¥{ev.price.toLocaleString()}</span>
          )}
          <span className="mono" style={{ fontSize: 9, color: 'var(--text-mute)', letterSpacing: '0.12em' }}>
            {ev.store_name} · {ev.source.toUpperCase()} · {fmtAgo(ev.detected_at)}
          </span>
          <span className="mono" style={{ fontSize: 9, color: 'var(--text-mute)' }}>
            conf {ev.confidence}
          </span>
        </div>
        {ev.raw_text && (
          <div className="jp" style={{ fontSize: 11, color: 'var(--text-mute)', marginTop: 5, lineHeight: 1.5 }}>
            {ev.raw_text.slice(0, 200)}{ev.raw_text.length > 200 ? '…' : ''}
          </div>
        )}
      </div>

      <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 6 }}>
        <a href={ev.source === 'twitter' ? `https://x.com/i/web/status/${ev.source_id}` : (ev.source_id.startsWith('http') ? ev.source_id : '#')}
          target="_blank" rel="noopener"
          className="mono" style={{
            fontSize: 9, letterSpacing: '0.15em', color: 'var(--rx-blue)',
            border: '1px solid var(--rx-blue)', padding: '4px 8px', textDecoration: 'none',
          }}>OPEN ↗</a>
      </div>
    </div>
  );
}

// ─── Stores tab ───────────────────────────────────────────
function StoresTab({ stores, onReload, onCollect, onDelete, onOpenAdd, onEdit }) {
  const grouped = useMemo(() => {
    const g = {};
    for (const s of stores) (g[s.category] ??= []).push(s);
    return g;
  }, [stores]);

  return (
    <div style={{ maxWidth: 1200, margin: '0 auto', padding: '16px 18px' }}>
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: 12, border: '1px solid var(--line)', background: 'var(--bg-panel)', marginBottom: 14,
      }} className="hud-corners">
        <span className="hud-c1" /><span className="hud-c2" />
        <div>
          <div className="mono" style={{ fontSize: 9, color: 'var(--rx-yellow)', letterSpacing: '0.22em', fontWeight: 700 }}>
            ▸ MONITORING STORES
          </div>
          <div className="disp" style={{ fontSize: 18, fontWeight: 800, color: '#f5f5f7', marginTop: 2 }}>
            {stores.length} STORES · {stores.filter(s => s.enabled).length} ACTIVE
          </div>
        </div>
        <button onClick={onOpenAdd} className="mono" style={btnAccent}>
          + ADD STORE
        </button>
      </div>

      {Object.entries(CFG.CATEGORIES).map(([cat, meta]) => {
        const arr = grouped[cat] ?? [];
        if (!arr.length) return null;
        return (
          <div key={cat} style={{ marginBottom: 16 }}>
            <div style={{
              display: 'flex', alignItems: 'center', gap: 10,
              padding: '7px 12px', background: 'var(--bg-panel-2)',
              borderLeft: `3px solid ${meta.color}`, marginBottom: 6,
            }}>
              <span className="disp" style={{
                fontSize: 11, fontWeight: 800, color: meta.color, letterSpacing: '0.18em',
              }}>{meta.label}</span>
              <span className="mono" style={{ fontSize: 9, color: 'var(--text-mute)', letterSpacing: '0.12em', marginLeft: 'auto' }}>
                {arr.length} STORES
              </span>
            </div>
            <div style={{ display: 'grid', gap: 6 }}>
              {arr.map(s => <StoreRow key={s.id} s={s} onCollect={onCollect} onDelete={onDelete} onEdit={onEdit} />)}
            </div>
          </div>
        );
      })}
    </div>
  );
}

function StoreRow({ s, onCollect, onDelete, onEdit }) {
  return (
    <div style={{
      display: 'grid', gridTemplateColumns: '46px 1fr auto',
      gap: 12, alignItems: 'center', padding: '10px 12px',
      border: '1px solid var(--line)', background: 'var(--bg-panel)',
    }}>
      <div style={{
        width: 42, height: 42, display: 'grid', placeItems: 'center',
        border: `1px solid ${s.color}`, color: s.color,
        fontFamily: 'var(--f-display)', fontWeight: 900, fontSize: 14,
      }}>{s.code}</div>

      <div>
        <div className="jp" style={{ fontSize: 14, fontWeight: 600, color: '#f5f5f7', marginBottom: 3 }}>
          {s.name}
        </div>
        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
          <span className="mono" style={{ fontSize: 9, color: 'var(--text-dim)', letterSpacing: '0.12em' }}>
            {s.sources.map(x => x.toUpperCase()).join(' · ')}
          </span>
          {s.x_handles.length > 0 && (
            <span className="mono" style={{ fontSize: 9, color: 'var(--rx-blue)' }}>
              @{s.x_handles.join(' @')}
            </span>
          )}
          <span className="mono" style={{ fontSize: 9, color: 'var(--text-mute)' }}>
            last: {fmtAgo(s.last_fetched)}
          </span>
          {!s.enabled && (
            <span className="mono" style={{ fontSize: 9, color: 'var(--rx-red)', letterSpacing: '0.12em' }}>
              PAUSED
            </span>
          )}
        </div>
      </div>

      <div style={{ display: 'flex', gap: 5 }}>
        <button onClick={() => onCollect(s)} className="mono" style={btnGhost} title="今すぐ収集">▶</button>
        <button onClick={() => onEdit(s)} className="mono" style={btnGhost} title="編集">✎</button>
        <button onClick={() => onDelete(s)} className="mono"
          style={{ ...btnGhost, color: 'var(--rx-red)', borderColor: 'var(--rx-red)' }} title="削除">✕</button>
      </div>
    </div>
  );
}

// ─── Add-store modal ──────────────────────────────────────
function AddStoreModal({ existingIds, onClose, onCreate }) {
  const [tab, setTab] = useState('catalog'); // catalog | custom
  const [msg, setMsg] = useState(null);
  const [busy, setBusy] = useState(false);

  const catalog = CFG.ADDABLE_STORES.filter(p => !existingIds.includes(p.id));

  const [form, setForm] = useState({
    id: '', name: '', code: '', color: '#00b8d9', category: 'custom',
    sources: ['web'], x_handles_str: '', web_urls_str: '',
  });

  const submit = async (payload) => {
    setBusy(true); setMsg(null);
    try {
      await onCreate(payload);
      onClose();
    } catch (e) {
      setMsg(String(e.message ?? e));
    } finally {
      setBusy(false);
    }
  };

  const submitCatalog = (preset) => submit({
    id: preset.id, name: preset.name, code: preset.code, color: preset.color,
    category: preset.category, sources: preset.sources,
    x_handles: preset.x_handles, web_urls: [],
  });

  const submitCustom = () => submit({
    id: form.id, name: form.name, code: form.code, color: form.color,
    category: form.category, sources: form.sources,
    x_handles: form.x_handles_str.split(/[\s,]+/).map(s => s.trim().replace(/^@/, '')).filter(Boolean),
    web_urls: form.web_urls_str.split(/[\s,]+/).map(s => s.trim()).filter(Boolean),
  });

  return (
    <ModalShell onClose={onClose} title="+ ADD STORE · 監視店舗を追加">
      <div style={{ display: 'flex', gap: 4, marginBottom: 14, borderBottom: '1px solid var(--line)' }}>
        {[['catalog', 'CATALOG · プリセット'], ['custom', 'CUSTOM · 自由入力']].map(([k, l]) => (
          <button key={k} onClick={() => setTab(k)} className="mono" style={{
            padding: '8px 14px', fontSize: 10, fontWeight: 800, letterSpacing: '0.16em',
            background: 'transparent',
            color: tab === k ? 'var(--rx-yellow)' : 'var(--text-dim)',
            border: 'none', borderBottom: `2px solid ${tab === k ? 'var(--rx-yellow)' : 'transparent'}`,
            cursor: 'pointer', marginBottom: -1,
          }}>{l}</button>
        ))}
      </div>

      {tab === 'catalog' && (
        <div style={{ display: 'grid', gap: 6, maxHeight: 460, overflow: 'auto' }} className="scroll-clean">
          {catalog.length === 0 && (
            <div style={emptyBox}>
              <span className="mono" style={{ fontSize: 10, color: 'var(--text-mute)' }}>
                すべてのプリセット店舗を追加済みです
              </span>
            </div>
          )}
          {catalog.map(p => (
            <div key={p.id} style={{
              display: 'grid', gridTemplateColumns: '46px 1fr auto',
              gap: 12, alignItems: 'center', padding: '10px 12px',
              border: '1px solid var(--line)', background: 'var(--bg-panel)',
            }}>
              <div style={{
                width: 42, height: 42, display: 'grid', placeItems: 'center',
                border: `1px solid ${p.color}`, color: p.color,
                fontFamily: 'var(--f-display)', fontWeight: 900, fontSize: 14,
              }}>{p.code}</div>
              <div>
                <div className="jp" style={{ fontSize: 14, fontWeight: 600, color: '#f5f5f7' }}>{p.name}</div>
                <div className="jp" style={{ fontSize: 11, color: 'var(--text-mute)', marginTop: 3 }}>{p.note}</div>
                <div style={{ display: 'flex', gap: 6, marginTop: 4 }}>
                  <CatChip category={p.category} />
                  <span className="mono" style={{ fontSize: 9, color: 'var(--text-dim)' }}>
                    {p.sources.map(s => s.toUpperCase()).join(' · ')}
                  </span>
                </div>
              </div>
              <button onClick={() => submitCatalog(p)} disabled={busy}
                className="mono" style={btnPrimary}>+ ADD</button>
            </div>
          ))}
        </div>
      )}

      {tab === 'custom' && (
        <div style={{ display: 'grid', gap: 10 }}>
          <FormRow label="ID (英数字, 2-32文字)">
            <input value={form.id} onChange={e => setForm({ ...form, id: e.target.value })}
              placeholder="e.g. mystore" style={inputStyle} />
          </FormRow>
          <FormRow label="店舗名">
            <input value={form.name} onChange={e => setForm({ ...form, name: e.target.value })}
              placeholder="e.g. マイホビーショップ" style={inputStyle} />
          </FormRow>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 120px', gap: 10 }}>
            <FormRow label="CODE (最大6文字)">
              <input value={form.code} onChange={e => setForm({ ...form, code: e.target.value.toUpperCase() })}
                maxLength={6} placeholder="MH" style={inputStyle} />
            </FormRow>
            <FormRow label="COLOR">
              <input type="color" value={form.color} onChange={e => setForm({ ...form, color: e.target.value })}
                style={{ ...inputStyle, height: 34, padding: 2 }} />
            </FormRow>
          </div>
          <FormRow label="カテゴリ">
            <select value={form.category} onChange={e => setForm({ ...form, category: e.target.value })}
              style={inputStyle}>
              {Object.entries(CFG.CATEGORIES).map(([k, v]) => (
                <option key={k} value={k}>{v.label}</option>
              ))}
            </select>
          </FormRow>
          <FormRow label="監視ソース">
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
              {CFG.SOURCES.map(src => {
                const on = form.sources.includes(src);
                return (
                  <button key={src} type="button"
                    onClick={() => setForm(f => ({
                      ...f, sources: on ? f.sources.filter(x => x !== src) : [...f.sources, src],
                    }))}
                    className="mono" style={{
                      padding: '5px 10px', fontSize: 10, letterSpacing: '0.14em', fontWeight: 700,
                      background: on ? 'var(--rx-yellow)' : 'transparent',
                      color: on ? '#05070c' : 'var(--text-dim)',
                      border: `1px solid ${on ? 'var(--rx-yellow)' : 'var(--line)'}`,
                      cursor: 'pointer',
                    }}>{src.toUpperCase()}</button>
                );
              })}
            </div>
          </FormRow>
          <FormRow label="X (Twitter) アカウント (カンマ区切り, @不要)">
            <input value={form.x_handles_str} onChange={e => setForm({ ...form, x_handles_str: e.target.value })}
              placeholder="e.g. mystore_official, mystore_bot" style={inputStyle} />
          </FormRow>
          <FormRow label="監視URL (改行 or カンマ区切り)">
            <textarea value={form.web_urls_str} onChange={e => setForm({ ...form, web_urls_str: e.target.value })}
              placeholder="https://example.com/stock" style={{ ...inputStyle, minHeight: 60, fontFamily: 'var(--f-mono)' }} />
          </FormRow>
          <button onClick={submitCustom} disabled={busy || !form.id || !form.name || !form.code}
            className="mono" style={{ ...btnAccent, padding: '10px 14px', width: '100%' }}>
            {busy ? '· CREATING ·' : '+ CREATE STORE'}
          </button>
        </div>
      )}

      {msg && (
        <div style={{
          marginTop: 12, padding: 10, border: '1px solid var(--rx-red)',
          background: 'rgba(230,57,70,0.1)', color: 'var(--rx-red)', fontSize: 12,
          fontFamily: 'var(--f-mono)',
        }}>ERROR · {msg}</div>
      )}
    </ModalShell>
  );
}

// ─── Edit-store modal (subset of Add) ─────────────────────
function EditStoreModal({ store, onClose, onSave }) {
  const [form, setForm] = useState({
    name: store.name, code: store.code, color: store.color,
    category: store.category, sources: store.sources,
    x_handles_str: store.x_handles.join(', '),
    web_urls_str: store.web_urls.join('\n'),
    enabled: store.enabled,
  });
  const [msg, setMsg] = useState(null);
  const [busy, setBusy] = useState(false);

  const save = async () => {
    setBusy(true); setMsg(null);
    try {
      await onSave({
        name: form.name, code: form.code, color: form.color,
        category: form.category, sources: form.sources,
        x_handles: form.x_handles_str.split(/[\s,]+/).map(s => s.trim().replace(/^@/, '')).filter(Boolean),
        web_urls: form.web_urls_str.split(/[\s,\n]+/).map(s => s.trim()).filter(Boolean),
        enabled: form.enabled,
      });
      onClose();
    } catch (e) {
      setMsg(String(e.message ?? e));
    } finally {
      setBusy(false);
    }
  };

  return (
    <ModalShell onClose={onClose} title={`✎ EDIT · ${store.name}`}>
      <div style={{ display: 'grid', gap: 10 }}>
        <FormRow label="店舗名">
          <input value={form.name} onChange={e => setForm({ ...form, name: e.target.value })} style={inputStyle} />
        </FormRow>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 120px', gap: 10 }}>
          <FormRow label="CODE">
            <input value={form.code} onChange={e => setForm({ ...form, code: e.target.value.toUpperCase() })}
              maxLength={6} style={inputStyle} />
          </FormRow>
          <FormRow label="COLOR">
            <input type="color" value={form.color} onChange={e => setForm({ ...form, color: e.target.value })}
              style={{ ...inputStyle, height: 34, padding: 2 }} />
          </FormRow>
        </div>
        <FormRow label="カテゴリ">
          <select value={form.category} onChange={e => setForm({ ...form, category: e.target.value })}
            style={inputStyle}>
            {Object.entries(CFG.CATEGORIES).map(([k, v]) => (
              <option key={k} value={k}>{v.label}</option>
            ))}
          </select>
        </FormRow>
        <FormRow label="監視ソース">
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
            {CFG.SOURCES.map(src => {
              const on = form.sources.includes(src);
              return (
                <button key={src} type="button"
                  onClick={() => setForm(f => ({
                    ...f, sources: on ? f.sources.filter(x => x !== src) : [...f.sources, src],
                  }))}
                  className="mono" style={{
                    padding: '5px 10px', fontSize: 10, letterSpacing: '0.14em', fontWeight: 700,
                    background: on ? 'var(--rx-yellow)' : 'transparent',
                    color: on ? '#05070c' : 'var(--text-dim)',
                    border: `1px solid ${on ? 'var(--rx-yellow)' : 'var(--line)'}`,
                    cursor: 'pointer',
                  }}>{src.toUpperCase()}</button>
              );
            })}
          </div>
        </FormRow>
        <FormRow label="X (Twitter) アカウント">
          <input value={form.x_handles_str} onChange={e => setForm({ ...form, x_handles_str: e.target.value })}
            style={inputStyle} />
        </FormRow>
        <FormRow label="監視URL">
          <textarea value={form.web_urls_str} onChange={e => setForm({ ...form, web_urls_str: e.target.value })}
            style={{ ...inputStyle, minHeight: 60, fontFamily: 'var(--f-mono)' }} />
        </FormRow>
        <label style={{ display: 'flex', gap: 8, alignItems: 'center', color: 'var(--text)', fontSize: 12 }}>
          <input type="checkbox" checked={form.enabled} onChange={e => setForm({ ...form, enabled: e.target.checked })} />
          <span className="mono" style={{ letterSpacing: '0.12em' }}>ENABLED · 自動監視ON</span>
        </label>
        <button onClick={save} disabled={busy}
          className="mono" style={{ ...btnAccent, padding: '10px 14px', width: '100%' }}>
          {busy ? '· SAVING ·' : '✓ SAVE CHANGES'}
        </button>
        {msg && (
          <div style={{
            padding: 10, border: '1px solid var(--rx-red)',
            background: 'rgba(230,57,70,0.1)', color: 'var(--rx-red)', fontSize: 12,
            fontFamily: 'var(--f-mono)',
          }}>ERROR · {msg}</div>
        )}
      </div>
    </ModalShell>
  );
}

// ─── Config tab ───────────────────────────────────────────
function ConfigTab({ stats, adminToken, setAdminToken, apiBase, setApiBase }) {
  return (
    <div style={{ maxWidth: 1200, margin: '0 auto', padding: '16px 18px', display: 'grid', gap: 14 }}>
      {/* Pipeline stats */}
      <div style={{ padding: 14, border: '1px solid var(--line)', background: 'var(--bg-panel)' }} className="hud-corners">
        <span className="hud-c1" /><span className="hud-c2" />
        <div className="mono" style={{ fontSize: 9, color: 'var(--rx-yellow)', letterSpacing: '0.22em', fontWeight: 700, marginBottom: 10 }}>
          ▸ PIPELINE · LAST 60 MIN
        </div>
        {stats ? (
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 10 }}>
            {[
              ['NITTER FETCHED', stats.pipeline.totals.nitter_fetched],
              ['PREFILTER PASS', stats.pipeline.totals.prefilter_pass],
              ['GROQ CALLED',    stats.pipeline.totals.groq_called],
              ['GROQ TOKENS',    stats.pipeline.totals.groq_tokens],
              ['NEW EVENTS',     stats.pipeline.totals.events_new],
            ].map(([l, v]) => (
              <div key={l} style={{ padding: 10, border: '1px solid var(--line)', background: 'var(--bg-panel-2)' }}>
                <div className="mono" style={{ fontSize: 8, color: 'var(--text-mute)', letterSpacing: '0.16em' }}>{l}</div>
                <div className="disp" style={{ fontSize: 20, fontWeight: 800, color: 'var(--rx-yellow)', marginTop: 4 }}>{v ?? 0}</div>
              </div>
            ))}
          </div>
        ) : (
          <div className="mono" style={{ fontSize: 10, color: 'var(--text-mute)' }}>Loading…</div>
        )}
      </div>

      {/* Groq quota */}
      {stats?.quota && (
        <div style={{ padding: 14, border: '1px solid var(--line)', background: 'var(--bg-panel)' }} className="hud-corners">
          <span className="hud-c1" /><span className="hud-c2" />
          <div className="mono" style={{ fontSize: 9, color: 'var(--rx-yellow)', letterSpacing: '0.22em', fontWeight: 700, marginBottom: 10 }}>
            ▸ GROQ QUOTA
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 10 }}>
            <QuotaBar label="REQ / DAY" cur={stats.quota.reqToday} max={stats.quota.limits.rpd} />
            <QuotaBar label="REQ / MIN" cur={stats.quota.reqPerMin} max={stats.quota.limits.rpm} />
            <QuotaBar label="TOK / MIN" cur={stats.quota.tokensPerMin} max={stats.quota.limits.tpm} />
          </div>
        </div>
      )}

      {/* Nitter health */}
      {stats?.nitter && (
        <div style={{ padding: 14, border: '1px solid var(--line)', background: 'var(--bg-panel)' }} className="hud-corners">
          <span className="hud-c1" /><span className="hud-c2" />
          <div className="mono" style={{ fontSize: 9, color: 'var(--rx-yellow)', letterSpacing: '0.22em', fontWeight: 700, marginBottom: 10 }}>
            ▸ NITTER POOL
          </div>
          <div style={{ display: 'grid', gap: 4 }}>
            {stats.nitter.map(n => (
              <div key={n.host} style={{
                display: 'grid', gridTemplateColumns: '10px 1fr 60px 60px',
                gap: 10, padding: '6px 10px', border: '1px solid var(--line)',
                background: 'var(--bg-panel-2)', alignItems: 'center',
              }}>
                <span style={{
                  width: 8, height: 8, borderRadius: 999,
                  background: n.alive ? 'var(--success)' : 'var(--rx-red)',
                }} />
                <span className="mono" style={{ fontSize: 11, color: 'var(--text)' }}>{n.host}</span>
                <span className="mono" style={{ fontSize: 10, color: 'var(--success)' }}>ok {n.ok}</span>
                <span className="mono" style={{ fontSize: 10, color: 'var(--rx-red)' }}>fail {n.fail}</span>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* Local settings */}
      <div style={{ padding: 14, border: '1px solid var(--line)', background: 'var(--bg-panel)' }} className="hud-corners">
        <span className="hud-c1" /><span className="hud-c2" />
        <div className="mono" style={{ fontSize: 9, color: 'var(--rx-yellow)', letterSpacing: '0.22em', fontWeight: 700, marginBottom: 10 }}>
          ▸ LOCAL SETTINGS
        </div>
        <div style={{ display: 'grid', gap: 10 }}>
          <FormRow label="API BASE (空欄=同一オリジン)">
            <input value={apiBase} onChange={e => setApiBase(e.target.value)}
              placeholder="https://g-intel-backend.<sub>.workers.dev" style={inputStyle} />
          </FormRow>
          <FormRow label="ADMIN TOKEN (書き込みAPI用 Bearer)">
            <input type="password" value={adminToken} onChange={e => setAdminToken(e.target.value)}
              placeholder="(CF Access使用時は不要)" style={inputStyle} />
          </FormRow>
          <div className="mono" style={{ fontSize: 10, color: 'var(--text-mute)', letterSpacing: '0.1em' }}>
            ↑ 値はブラウザのlocalStorageに保存されます。
          </div>
        </div>
      </div>
    </div>
  );
}

function QuotaBar({ label, cur, max }) {
  const pct = Math.min(100, Math.round((cur / max) * 100));
  const color = pct > 85 ? 'var(--rx-red)' : pct > 60 ? 'var(--rx-yellow)' : 'var(--success)';
  return (
    <div style={{ padding: 10, border: '1px solid var(--line)', background: 'var(--bg-panel-2)' }}>
      <div className="mono" style={{ fontSize: 8, color: 'var(--text-mute)', letterSpacing: '0.16em' }}>{label}</div>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 6, marginTop: 4 }}>
        <span className="disp" style={{ fontSize: 20, fontWeight: 800, color }}>{cur}</span>
        <span className="mono" style={{ fontSize: 10, color: 'var(--text-mute)' }}>/ {max}</span>
      </div>
      <div style={{ height: 4, background: 'var(--line)', marginTop: 6 }}>
        <div style={{ height: '100%', width: `${pct}%`, background: color, transition: 'width .3s' }} />
      </div>
    </div>
  );
}

// ─── Shared UI atoms ──────────────────────────────────────
function ModalShell({ title, onClose, children }) {
  return (
    <div style={{
      position: 'fixed', inset: 0, background: 'rgba(5,7,12,0.85)',
      zIndex: 100, display: 'grid', placeItems: 'center', padding: 20,
    }} onClick={onClose}>
      <div onClick={e => e.stopPropagation()} style={{
        width: '100%', maxWidth: 560, maxHeight: '90vh', overflow: 'auto',
        background: 'var(--bg-panel)', border: '1px solid var(--line-hot)',
        padding: 18, position: 'relative',
      }} className="scroll-clean hud-corners">
        <span className="hud-c1" /><span className="hud-c2" />
        <div style={{
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          borderBottom: '1px solid var(--line)', paddingBottom: 10, marginBottom: 12,
        }}>
          <span className="disp" style={{ fontSize: 13, fontWeight: 800, color: 'var(--rx-yellow)', letterSpacing: '0.14em' }}>
            {title}
          </span>
          <button onClick={onClose} className="mono" style={{
            background: 'transparent', color: 'var(--text-mute)', border: 'none',
            fontSize: 16, cursor: 'pointer', padding: '0 4px',
          }}>✕</button>
        </div>
        {children}
      </div>
    </div>
  );
}

function FormRow({ label, children }) {
  return (
    <label style={{ display: 'grid', gap: 4 }}>
      <span className="mono" style={{ fontSize: 9, color: 'var(--text-dim)', letterSpacing: '0.16em' }}>
        {label}
      </span>
      {children}
    </label>
  );
}

// ─── Styles ───────────────────────────────────────────────
const inputStyle = {
  background: 'var(--bg-void)', color: 'var(--text)', border: '1px solid var(--line)',
  padding: '8px 10px', fontFamily: 'var(--f-body)', fontSize: 13, outline: 'none',
};
const selectStyle = { ...inputStyle, fontFamily: 'var(--f-mono)', fontSize: 11, padding: '6px 8px' };
const btnPrimary = {
  background: 'var(--rx-blue)', color: '#fff', border: '1px solid var(--rx-blue)',
  padding: '6px 12px', fontSize: 10, fontWeight: 800, letterSpacing: '0.14em', cursor: 'pointer',
};
const btnAccent = {
  background: 'var(--rx-yellow)', color: '#05070c', border: '1px solid var(--rx-yellow)',
  padding: '6px 14px', fontSize: 10, fontWeight: 800, letterSpacing: '0.14em', cursor: 'pointer',
};
const btnGhost = {
  background: 'transparent', color: 'var(--text-dim)', border: '1px solid var(--line)',
  padding: '6px 10px', fontSize: 12, cursor: 'pointer', letterSpacing: '0.1em',
};
const emptyBox = {
  padding: 24, textAlign: 'center', border: '1px dashed var(--line-hot)',
  background: 'var(--bg-panel)',
};

// ─── Root ────────────────────────────────────────────────
function App() {
  const [tab, setTab] = useState(() => localStorage.getItem('gintel_tab') || 'EVENTS');
  const [health, setHealth] = useState(false);
  const [mode, setMode] = useState(null);
  const [events, setEvents] = useState([]);
  const [stores, setStores] = useState([]);
  const [stats, setStats] = useState(null);
  const [loading, setLoading] = useState(false);
  const [filter, setFilter] = useState({ storeId: '', status: '' });
  const [showAdd, setShowAdd] = useState(false);
  const [editStore, setEditStore] = useState(null);
  const [flash, setFlash] = useState(null);
  const [apiBase, setApiBase] = useState(() => localStorage.getItem('GINTEL_API_BASE') || '');
  const [adminToken, setAdminToken] = useState(() => localStorage.getItem('GINTEL_ADMIN_TOKEN') || '');

  useEffect(() => { localStorage.setItem('gintel_tab', tab); }, [tab]);
  useEffect(() => {
    localStorage.setItem('GINTEL_API_BASE', apiBase);
    CFG.API_BASE = apiBase;
  }, [apiBase]);
  useEffect(() => {
    localStorage.setItem('GINTEL_ADMIN_TOKEN', adminToken);
    CFG.ADMIN_TOKEN = adminToken;
  }, [adminToken]);

  const showFlash = (msg, kind = 'ok') => {
    setFlash({ msg, kind });
    setTimeout(() => setFlash(null), 3000);
  };

  const loadHealth = useCallback(async () => {
    try { const j = await api.get('/api/health'); setHealth(!!j.ok); setMode(j.mode); }
    catch { setHealth(false); }
  }, []);

  const loadEvents = useCallback(async () => {
    setLoading(true);
    try {
      const p = new URLSearchParams({ limit: '100' });
      if (filter.storeId) p.set('store_id', filter.storeId);
      if (filter.status)  p.set('status',   filter.status);
      const list = await api.get(`/api/events?${p}`);
      setEvents(Array.isArray(list) ? list : []);
    } catch (e) { showFlash(String(e.message ?? e), 'err'); }
    finally { setLoading(false); }
  }, [filter]);

  const loadStores = useCallback(async () => {
    try { setStores(await api.get('/api/stores')); }
    catch (e) { showFlash(String(e.message ?? e), 'err'); }
  }, []);

  const loadStats = useCallback(async () => {
    try { setStats(await api.get('/api/pipeline-stats?minutes=60')); }
    catch { /* silent */ }
  }, []);

  // Initial + polling
  useEffect(() => { loadHealth(); loadStores(); loadEvents(); loadStats(); }, []);
  useEffect(() => { loadEvents(); }, [filter]);
  useEffect(() => {
    const t = setInterval(() => {
      loadHealth();
      if (tab === 'EVENTS') loadEvents();
      if (tab === 'STORES') loadStores();
      if (tab === 'CONFIG') loadStats();
    }, 30_000);
    return () => clearInterval(t);
  }, [tab, loadHealth, loadEvents, loadStores, loadStats]);

  const createStore = async (payload) => {
    await api.send('POST', '/api/stores', payload);
    await loadStores();
    showFlash(`+ ADDED · ${payload.name}`);
  };
  const updateStore = async (id, payload) => {
    await api.send('PATCH', `/api/stores/${id}`, payload);
    await loadStores();
    showFlash(`✓ UPDATED · ${payload.name ?? id}`);
  };
  const collectStore = async (s) => {
    try {
      await api.send('POST', `/api/collect/${s.id}`);
      showFlash(`▶ TRIGGERED · ${s.name}`);
    } catch (e) { showFlash(String(e.message ?? e), 'err'); }
  };
  const deleteStore = async (s) => {
    if (!confirm(`「${s.name}」を削除しますか?\n関連する検知イベントも削除されます。`)) return;
    try {
      await api.send('DELETE', `/api/stores/${s.id}`);
      await loadStores();
      showFlash(`✕ DELETED · ${s.name}`);
    } catch (e) { showFlash(String(e.message ?? e), 'err'); }
  };

  return (
    <div>
      <TopBar health={health} tab={tab} onTab={setTab} mode={mode} />

      {tab === 'EVENTS' && (
        <EventsTab events={events} stores={stores} loading={loading}
          onRefresh={loadEvents} filter={filter} setFilter={setFilter} />
      )}
      {tab === 'STORES' && (
        <StoresTab stores={stores} onReload={loadStores}
          onOpenAdd={() => setShowAdd(true)}
          onCollect={collectStore} onDelete={deleteStore}
          onEdit={s => setEditStore(s)} />
      )}
      {tab === 'CONFIG' && (
        <ConfigTab stats={stats}
          apiBase={apiBase} setApiBase={setApiBase}
          adminToken={adminToken} setAdminToken={setAdminToken} />
      )}

      {showAdd && (
        <AddStoreModal
          existingIds={stores.map(s => s.id)}
          onClose={() => setShowAdd(false)}
          onCreate={createStore} />
      )}
      {editStore && (
        <EditStoreModal
          store={editStore}
          onClose={() => setEditStore(null)}
          onSave={(payload) => updateStore(editStore.id, payload)} />
      )}

      {flash && (
        <div style={{
          position: 'fixed', bottom: 20, right: 20, zIndex: 200,
          padding: '10px 14px', minWidth: 220,
          background: 'var(--bg-panel)',
          border: `1px solid ${flash.kind === 'err' ? 'var(--rx-red)' : 'var(--success)'}`,
          color: flash.kind === 'err' ? 'var(--rx-red)' : 'var(--success)',
          fontFamily: 'var(--f-mono)', fontSize: 11, letterSpacing: '0.1em',
        }}>{flash.msg}</div>
      )}
    </div>
  );
}

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