/* global React */
// Pokedex + Wiki + Audit Log + stub pages.

function PokedexPage({ toast }) {
  const [tab, setTab] = React.useState('mons'); // mons | npc
  const [filter, setFilter] = React.useState('all'); // all | missing | recent
  const [search, setSearch] = React.useState('');
  const [openItem, setOpenItem] = React.useState(null);
  const [pool, setPool] = React.useState(window.MOCK.POKEDEX);

  React.useEffect(() => {
    Promise.all([window.API.imageManifest(), window.API.lineups()]).then(([m, lineups]) => {
      // 收集 simple_match.json 里所有出现过的精灵名字
      const seen = new Set();
      for (const groups of Object.values(lineups || {})) {
        for (const g of groups) for (const l of g.lineupList || []) for (const p of l.playerList || []) {
          if (p.name) seen.add(p.name);
        }
      }
      const have = new Set(tab === 'mons' ? (m?.pet || []) : (m?.npc || []));
      // pool = (referenced ∪ have)
      const names = Array.from(new Set([...have, ...(tab === 'mons' ? seen : [])]));
      const list = names.map((n, idx) => ({
        id: idx + 1, name: n,
        color: window.MOCK.POKEMON.find((x) => x.name === n)?.color || '#999',
        types: window.MOCK.POKEMON.find((x) => x.name === n)?.types || [],
        size_kb: 0,
        missing: !have.has(n),
        recent: false,
        url: '/img/' + (tab === 'mons' ? 'pet' : 'npc') + '/' + encodeURIComponent(n) + '.png',
      }));
      setPool(list);
    }).catch(() => {});
  }, [tab]);

  const items = React.useMemo(() => {
    return pool.filter((p) => {
      if (filter === 'missing' && !p.missing) return false;
      if (filter === 'recent' && !p.recent) return false;
      if (search && !p.name.toLowerCase().includes(search.toLowerCase()) && !`#${p.id}`.includes(search)) return false;
      return true;
    });
  }, [filter, search, pool]);

  const stats = {
    total: pool.length,
    missing: pool.filter((p) => p.missing).length,
    recent: pool.filter((p) => p.recent).length,
  };

  return (
    <div>
      <div className="page-header">
        <div>
          <div className="breadcrumb"><span>资源</span><span className="sep">/</span><span>图鉴</span></div>
          <h1 className="page-title">图鉴管理</h1>
          <div className="page-subtitle">
            共 {stats.total} 张精灵图 ·
            <span style={{ color: 'var(--danger)' }}> {stats.missing} 缺图</span> ·
            最近替换 {stats.recent} 张
          </div>
        </div>
        <div className="hstack">
          <window.Btn icon={<window.I.download size={14}/>}>导出 zip</window.Btn>
          <window.Btn icon={<window.I.upload size={14}/>}>批量上传 zip</window.Btn>
          <window.Btn kind="primary" icon={<window.I.plus size={14}/>}>上传图片</window.Btn>
        </div>
      </div>

      {/* Tabs */}
      <div className="hstack" style={{ marginBottom: 16, justifyContent:'space-between', flexWrap:'wrap' }}>
        <div style={{ display:'inline-flex', gap: 4, background: 'var(--surface-2)', border:'1px solid var(--border)', borderRadius:'var(--r-md)', padding: 3 }}>
          <TabPill active={tab === 'mons'} onClick={() => setTab('mons')}>精灵图 · 230</TabPill>
          <TabPill active={tab === 'npc'} onClick={() => setTab('npc')}>NPC 头像 · 27</TabPill>
        </div>
        <div className="hstack" style={{ gap: 8 }}>
          <div style={{ display:'inline-flex', gap: 4 }}>
            <Chip2 active={filter === 'all'} onClick={() => setFilter('all')}>全部 {stats.total}</Chip2>
            <Chip2 active={filter === 'missing'} onClick={() => setFilter('missing')} danger>
              <span style={{ display:'inline-block', width:6, height:6, borderRadius:'50%', background:'var(--danger)', marginRight:6 }}/>
              缺图 {stats.missing}
            </Chip2>
            <Chip2 active={filter === 'recent'} onClick={() => setFilter('recent')}>最近替换 {stats.recent}</Chip2>
          </div>
          <div style={{ position:'relative' }}>
            <window.I.search size={13} style={{ position:'absolute', left: 10, top: 11, color: 'var(--text-3)' }}/>
            <input className="input" placeholder="搜索 #ID / 名称" value={search}
                   onChange={(e) => setSearch(e.target.value)}
                   style={{ paddingLeft: 32, height: 34, width: 200 }}/>
          </div>
        </div>
      </div>

      {/* Grid */}
      <div className="card card-pad-sm" style={{ padding: 16 }}>
        <div style={{
          display:'grid',
          gridTemplateColumns: 'repeat(auto-fill, minmax(96px, 1fr))',
          gap: 10,
        }}>
          {items.map((p) => (
            <PokedexTile key={p.id} p={p} onClick={() => setOpenItem(p)}/>
          ))}
        </div>
        {items.length === 0 && (
          <window.Empty title="没有匹配的项" hint="试试切换过滤器"/>
        )}
      </div>

      {/* Detail drawer */}
      {openItem && (
        <>
          <div className="drawer-backdrop" onClick={() => setOpenItem(null)}/>
          <div className="drawer" style={{ width: 480 }}>
            <PokedexDetail p={openItem} onClose={() => setOpenItem(null)} toast={toast}/>
          </div>
        </>
      )}
    </div>
  );
}

function PokedexTile({ p, onClick }) {
  return (
    <button onClick={onClick} style={{
      padding: 8,
      border: `1px solid ${p.missing ? 'var(--danger)' : 'var(--border)'}`,
      borderRadius: 'var(--r-md)',
      background: p.missing ? 'var(--danger-bg)' : 'var(--surface)',
      borderStyle: p.missing ? 'dashed' : 'solid',
      cursor: 'pointer',
      transition: 'transform .08s, border-color .12s',
      position: 'relative',
      textAlign: 'left',
    }}
    onMouseEnter={(e) => e.currentTarget.style.transform = 'scale(1.04)'}
    onMouseLeave={(e) => e.currentTarget.style.transform = 'scale(1)'}>
      <div style={{ textAlign:'center' }}>
        {p.missing || !p.url ? (
          <window.Sprite name={p.name} color={p.color} size="lg" missing={p.missing}/>
        ) : (
          <div style={{ width: 64, height: 64, margin: '0 auto', background: 'var(--surface-2)', border: '1px solid var(--border)', borderRadius: 'var(--r-md)', overflow: 'hidden', display:'grid', placeItems:'center' }}>
            <img src={p.url} alt={p.name} style={{ width: '100%', height: '100%', objectFit: 'contain' }} onError={(e) => { e.currentTarget.style.display = 'none'; }}/>
          </div>
        )}
      </div>
      <div className="mono" style={{ fontSize: 10, color: 'var(--text-3)', marginTop: 6 }}>#{String(p.id).padStart(3, '0')}</div>
      <div style={{ fontSize: 12, fontWeight: 500, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>{p.name}</div>
      {p.recent && !p.missing && (
        <span style={{ position:'absolute', top: 6, right: 6, width: 6, height: 6, borderRadius: '50%', background: 'var(--success)' }}/>
      )}
    </button>
  );
}

function PokedexDetail({ p, onClose, toast }) {
  return (
    <>
      <div className="drawer-header">
        <div>
          <div className="hstack">
            <span className="mono" style={{ color: 'var(--text-3)' }}>#{String(p.id).padStart(3, '0')}</span>
            <span style={{ fontWeight: 600, fontSize: 16 }}>{p.name}</span>
            {p.missing && <window.Pill status="rejected">缺图</window.Pill>}
          </div>
          <div style={{ display:'flex', gap: 4, marginTop: 6 }}>
            {p.types.map((t) => <window.TypeChip key={t} type={t}/>)}
          </div>
        </div>
        <button className="icon-btn" onClick={onClose}><window.I.x size={16}/></button>
      </div>
      <div className="drawer-body">
        <div style={{
          padding: 32, textAlign:'center',
          background: p.missing ? 'var(--danger-bg)' : 'var(--surface-2)',
          border: `1px ${p.missing ? 'dashed' : 'solid'} ${p.missing ? 'var(--danger)' : 'var(--border)'}`,
          borderRadius: 'var(--r-lg)',
          marginBottom: 16,
        }}>
          {p.missing ? (
            <div style={{ color: 'var(--danger)' }}>
              <window.I.alert size={40}/>
              <div style={{ marginTop: 12, fontWeight: 500 }}>该精灵尚无图片</div>
              <div className="muted" style={{ fontSize: 12, marginTop: 4 }}>上传一张 PNG / WebP, 推荐 96×96</div>
            </div>
          ) : (
            <div style={{ transform: 'scale(2.2)', display:'inline-block' }}>
              <window.Sprite name={p.name} color={p.color} size="lg"/>
            </div>
          )}
        </div>

        <div style={{ display:'grid', gridTemplateColumns: '1fr 1fr', gap: 10, fontSize: 13 }}>
          <Meta label="文件名" mono>{`${p.id}.png`}</Meta>
          <Meta label="大小">{p.missing ? '—' : `${p.size_kb} KB`}</Meta>
          <Meta label="分辨率">{p.missing ? '—' : '96 × 96'}</Meta>
          <Meta label="上传时间">{p.missing ? '—' : '2025-11-12 14:02'}</Meta>
        </div>

        <div className="divider"/>
        <div style={{
          padding: 12,
          background: 'var(--surface-2)',
          border:'1px dashed var(--border-strong)',
          borderRadius: 'var(--r-md)',
          textAlign:'center', color: 'var(--text-2)', fontSize: 13,
        }}>
          <window.I.upload size={20}/>
          <div style={{ marginTop: 6 }}>拖拽图片到此处替换,或点击选择文件</div>
        </div>
      </div>
      <div className="drawer-footer">
        <button className="btn danger" onClick={() => { toast.push(`已删除 #${p.id} 的图片`); onClose(); }}>
          <window.I.trash size={13}/> 删除
        </button>
        <div className="hstack">
          <window.Btn onClick={() => toast.push('已复制 URL')}>复制 URL</window.Btn>
          <window.Btn kind="primary" onClick={() => { toast.push(`#${p.id} 图片已替换`); onClose(); }}>
            {p.missing ? '上传' : '替换'}
          </window.Btn>
        </div>
      </div>
    </>
  );
}

function Meta({ label, children, mono }) {
  return (
    <div>
      <div className="muted" style={{ fontSize: 11, marginBottom: 2 }}>{label}</div>
      <div className={mono ? 'mono' : ''} style={{ fontWeight: 500 }}>{children}</div>
    </div>
  );
}

function TabPill({ active, onClick, children }) {
  return (
    <button onClick={onClick} style={{
      padding: '6px 14px', fontSize: 13, fontWeight: 500,
      borderRadius: 'var(--r-sm)',
      background: active ? 'var(--surface)' : 'transparent',
      color: active ? 'var(--text)' : 'var(--text-2)',
      boxShadow: active ? 'var(--shadow-sm)' : 'none',
      border: active ? '1px solid var(--border)' : '1px solid transparent',
    }}>{children}</button>
  );
}

function Chip2({ active, onClick, children, danger }) {
  return (
    <button onClick={onClick} style={{
      padding: '5px 11px', fontSize: 12, fontWeight: 500,
      borderRadius: 'var(--r-pill)',
      border: '1px solid var(--border)',
      background: active ? 'var(--surface)' : 'transparent',
      color: active ? 'var(--text)' : 'var(--text-2)',
      whiteSpace:'nowrap',
    }}>{children}</button>
  );
}

// ────────────────────────────────────────────────────────────────────
// Wiki Editor

function WikiPage({ toast }) {
  const [openId, setOpenId] = React.useState(null);
  const [docs, setDocs] = React.useState(window.MOCK.WIKI);
  React.useEffect(() => {
    // 1. 拉静态站内攻略
    window.API.docsIndex().then((d) => {
      if (Array.isArray(d) && d.length) {
        const staticDocs = d.slice(0, 200).map((x) => ({
          id: 'static-' + x.id, title: x.title, author: x.author || '未署名',
          updated: x.createAt || '', status: x.outLink ? 'outlink' : 'published',
          tags: [], _static: true, _id: x.id,
        }));
        setDocs(staticDocs);
      }
    }).catch(() => {});
    // 2. 拉 D1 drafts
    window.API.wikiList().then((r) => {
      if (r?.items?.length) {
        const drafts = r.items.map((x) => ({
          id: x.id, title: x.title, author: x.author || '管理员',
          updated: x.updated_at ? new Date(x.updated_at).toLocaleString('zh-CN', { hour12: false }) : '',
          status: x.status, tags: x.tags ? (JSON.parse(x.tags) || []) : [],
        }));
        setDocs((d) => [...drafts, ...d.filter((x) => x._static)]);
      }
    }).catch(() => {});
  }, []);
  const open = openId ? docs.find((w) => w.id === openId) : null;

  return (
    <div>
      <div className="page-header">
        <div>
          <div className="breadcrumb"><span>日常</span><span className="sep">/</span><span>攻略</span></div>
          <h1 className="page-title">攻略管理</h1>
          <div className="page-subtitle">共 {docs.length} 篇 · {docs.filter((d) => d.status==='published').length} 已发布 · {docs.filter((d) => d.status==='draft').length} 草稿</div>
        </div>
        <window.Btn kind="primary" icon={<window.I.plus size={14}/>} onClick={() => setOpenId('new')}>
          新建攻略
        </window.Btn>
      </div>

      <div className="card" style={{ padding: 0, overflow:'hidden' }}>
        <table className="table">
          <thead>
            <tr>
              <th>标题</th>
              <th style={{ width: 100 }}>状态</th>
              <th style={{ width: 100 }}>作者</th>
              <th style={{ width: 140 }}>更新时间</th>
              <th style={{ width: 200 }}>标签</th>
              <th style={{ width: 120 }}></th>
            </tr>
          </thead>
          <tbody>
            {docs.map((w) => (
              <tr key={w.id}>
                <td>
                  <button onClick={() => setOpenId(w.id)} style={{ fontWeight: 500, color: 'var(--text)', textAlign:'left' }}>
                    {w.title}
                  </button>
                </td>
                <td>
                  <WikiStatusPill status={w.status}/>
                </td>
                <td className="muted">{w.author}</td>
                <td className="mono" style={{ color: 'var(--text-2)', fontSize: 12 }}>{w.updated}</td>
                <td>
                  <div style={{ display:'flex', gap: 4 }}>
                    {w.tags.map((t) => <Chip key={t}>{t}</Chip>)}
                  </div>
                </td>
                <td>
                  <div style={{ display:'flex', gap: 4, justifyContent:'flex-end' }}>
                    <window.Btn size="xs" icon={<window.I.edit size={11}/>} onClick={() => setOpenId(w.id)}>编辑</window.Btn>
                    <window.Btn size="xs" kind="danger" icon={<window.I.trash size={11}/>}>删除</window.Btn>
                  </div>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      {openId && (
        <>
          <div className="drawer-backdrop" onClick={() => setOpenId(null)}/>
          <div className="drawer wide">
            <WikiEditor article={open} onClose={() => setOpenId(null)} toast={toast}/>
          </div>
        </>
      )}
    </div>
  );
}

function WikiStatusPill({ status }) {
  const map = {
    published: { c: 'approved', t: '已发布' },
    draft:     { c: 'pending',  t: '草稿' },
    outlink:   { c: 'merged',   t: '外链' },
    deleted:   { c: 'rejected', t: '已删除' },
  };
  const m = map[status] || map.draft;
  return <window.Pill status={m.c}>{m.t}</window.Pill>;
}

function WikiEditor({ article, onClose, toast }) {
  const isNew = !article;
  return (
    <>
      <div className="drawer-header">
        <div>
          <div style={{ fontWeight: 600, fontSize: 16 }}>{isNew ? '新建攻略' : article.title}</div>
          <div className="muted" style={{ fontSize: 12, marginTop: 4 }}>
            {isNew ? '草稿尚未保存' : `${article.updated} · 草稿自动保存于 12 秒前`}
          </div>
        </div>
        <button className="icon-btn" onClick={onClose}><window.I.x size={16}/></button>
      </div>

      <div className="drawer-body" style={{ padding: 0, display:'grid', gridTemplateColumns: '1fr 280px' }}>
        {/* Editor */}
        <div style={{ padding: 20, borderRight: '1px solid var(--border)' }}>
          {/* Toolbar */}
          <div style={{
            display:'flex', gap: 2,
            padding: '4px 6px',
            background: 'var(--surface-2)',
            border:'1px solid var(--border)',
            borderRadius: 'var(--r-md)',
            marginBottom: 12,
            flexWrap:'wrap',
          }}>
            {['H1','H2','B','I','U','S'].map((t) => (
              <button key={t} className="btn xs ghost" style={{ width: 26, padding: 0, justifyContent:'center' }}>{t}</button>
            ))}
            <span style={{ width: 1, background: 'var(--border)', margin: '0 4px' }}/>
            <button className="btn xs ghost"><window.I.list size={12}/></button>
            <button className="btn xs ghost">引用</button>
            <button className="btn xs ghost">代码</button>
            <button className="btn xs ghost"><window.I.upload size={12}/> 图片</button>
            <button className="btn xs ghost">表格</button>
            <span style={{ width: 1, background: 'var(--border)', margin: '0 4px' }}/>
            <button className="btn xs ghost">链接</button>
          </div>

          <input className="input" defaultValue={article?.title || ''} placeholder="标题…"
                 style={{ fontSize: 22, fontWeight: 600, height: 'auto', padding: '8px 12px', border: 0, background:'transparent' }}/>

          <div style={{ fontSize: 14, lineHeight: 1.7, padding: '8px 12px', color: 'var(--text)' }}>
            {isNew ? (
              <p style={{ color: 'var(--text-3)' }}>从这里开始写作…</p>
            ) : (
              <>
                <p style={{ marginBottom: 12 }}>小茂作为关都四天王之一,出场频率较高。本攻略汇总常见的 12 套阵容,按 <b>速度档位</b> 与 <b>开局组合</b> 分组。</p>
                <h3 style={{ marginTop: 24, marginBottom: 8, fontSize: 16, fontWeight: 600 }}>S+ 速度 · 定锁组合</h3>
                <p style={{ marginBottom: 12 }}>这是最常见的开局,玩家通常带<u>讲究头巾</u>的喷火龙开盘:</p>
                <ul style={{ paddingLeft: 20, marginBottom: 12, listStyle:'disc' }}>
                  <li>第 1 只: <b>喷火龙</b> @讲究头巾</li>
                  <li>第 2 只: <b>钢铁人</b> @气球</li>
                </ul>
                <blockquote style={{
                  borderLeft: '3px solid var(--indigo)',
                  paddingLeft: 14, color: 'var(--text-2)', fontStyle: 'italic',
                  margin: '12px 0',
                }}>注: 该组合在面对锁血队时表现较差,详见下文。</blockquote>
                <pre style={{
                  background: 'var(--surface-2)', padding: 12,
                  borderRadius: 'var(--r-md)', fontSize: 12,
                  fontFamily:'var(--font-mono)',
                }}>{`function calcSpeed(level, ev) {
  return Math.floor((level * 2 + ev / 4) * 1.1);
}`}</pre>
              </>
            )}
          </div>
        </div>

        {/* Sidebar metadata */}
        <div style={{ padding: 20, background: 'var(--surface-2)', overflowY: 'auto' }}>
          <label className="field">状态</label>
          <select className="select" defaultValue={article?.status || 'draft'}>
            <option value="draft">草稿</option>
            <option value="published">发布</option>
            <option value="outlink">外链</option>
          </select>

          <label className="field" style={{ marginTop: 16 }}>作者</label>
          <input className="input" defaultValue={article?.author || '管理员'}/>

          <label className="field" style={{ marginTop: 16 }}>标签</label>
          <div style={{ display:'flex', gap: 4, flexWrap:'wrap', marginBottom: 6 }}>
            {(article?.tags || []).map((t) => (
              <span key={t} style={{
                display:'inline-flex', alignItems:'center', gap: 4,
                fontSize: 12, padding: '3px 8px',
                background: 'var(--surface)', border:'1px solid var(--border)',
                borderRadius:'var(--r-pill)',
              }}>{t} <window.I.x size={10}/></span>
            ))}
          </div>
          <input className="input" placeholder="+ 添加标签" style={{ height: 28 }}/>

          <label className="field" style={{ marginTop: 16 }}>外链 URL</label>
          <input className="input" placeholder="https://..."/>

          <label className="field" style={{ marginTop: 16 }}>发布时间</label>
          <input className="input" defaultValue="立即发布" readOnly/>

          <div className="divider"/>
          <div className="muted" style={{ fontSize: 11, marginBottom: 8 }}>关联图片 · 3</div>
          <div style={{ display:'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 6 }}>
            {[1,2,3].map((i) => (
              <div key={i} style={{ aspectRatio: '1', background: 'var(--surface-3)', borderRadius: 'var(--r-sm)', display:'grid', placeItems:'center', color: 'var(--text-3)', fontSize: 10 }}>
                IMG {i}
              </div>
            ))}
          </div>
        </div>
      </div>

      <div className="drawer-footer">
        <div className="muted" style={{ fontSize: 12 }}>
          <window.I.check size={12} style={{ verticalAlign: 'middle', color: 'var(--success)' }}/> 草稿已自动保存
        </div>
        <div className="hstack">
          <window.Btn onClick={onClose}>关闭</window.Btn>
          <window.Btn kind="primary" onClick={() => { toast.push('已发布'); onClose(); }}>
            发布
          </window.Btn>
        </div>
      </div>
    </>
  );
}

// ────────────────────────────────────────────────────────────────────
// Audit Log

function AuditLogPage() {
  const [openId, setOpenId] = React.useState(null);
  const [filter, setFilter] = React.useState('all');
  const [items, setItems] = React.useState(window.MOCK.AUDIT);

  React.useEffect(() => {
    window.API.audit(200).then((r) => {
      if (r?.items?.length) {
        setItems(r.items.map((x) => ({
          id: 'a' + x.id, at: new Date(x.at).toLocaleTimeString('zh-CN', { hour12: false }),
          actor: x.actor, action: x.action, target: x.target || '',
          desc: x.description || '',
          _raw: x,
        })));
      }
    }).catch(() => {});
  }, []);

  const open = openId ? items.find((a) => a.id === openId) : null;
  const filtered = items.filter((a) => {
    if (filter === 'all') return true;
    return a.action.startsWith(filter);
  });

  // Group by hour
  const groups = {};
  filtered.forEach((a) => {
    const hour = a.at.split(':')[0] + ':00';
    (groups[hour] = groups[hour] || []).push(a);
  });

  return (
    <div>
      <div className="page-header">
        <div>
          <div className="breadcrumb"><span>系统</span><span className="sep">/</span><span>操作日志</span></div>
          <h1 className="page-title">操作日志</h1>
          <div className="page-subtitle">操作时间线 · 共 {items.length} 条</div>
        </div>
        <window.Btn icon={<window.I.download size={14}/>}>导出 JSON</window.Btn>
      </div>

      <div className="hstack" style={{ marginBottom: 16, flexWrap:'wrap' }}>
        <div style={{ display:'inline-flex', gap: 4 }}>
          <Chip2 active={filter === 'all'}      onClick={() => setFilter('all')}>全部</Chip2>
          <Chip2 active={filter === 'submission'} onClick={() => setFilter('submission')}>审核</Chip2>
          <Chip2 active={filter === 'data'}     onClick={() => setFilter('data')}>数据编辑</Chip2>
          <Chip2 active={filter === 'wiki'}     onClick={() => setFilter('wiki')}>攻略</Chip2>
          <Chip2 active={filter === 'pokedex'}  onClick={() => setFilter('pokedex')}>图鉴</Chip2>
          <Chip2 active={filter === 'scrape'}   onClick={() => setFilter('scrape')}>抓取</Chip2>
          <Chip2 active={filter === 'deploy'}   onClick={() => setFilter('deploy')}>部署</Chip2>
        </div>
        <div style={{ flex: 1 }}/>
        <input className="input" placeholder="搜索 actor / target / desc" style={{ width: 240 }}/>
        <window.Btn icon={<window.I.clock size={14}/>}>今天</window.Btn>
      </div>

      <div className="card" style={{ padding: 0, overflow:'hidden' }}>
        {Object.entries(groups).map(([hour, items]) => (
          <div key={hour}>
            <div style={{
              padding: '8px 20px', fontSize: 11,
              textTransform: 'uppercase', letterSpacing:'.06em',
              color: 'var(--text-3)', fontFamily: 'var(--font-mono)',
              background: 'var(--surface-2)',
              borderBottom: '1px solid var(--border)',
              borderTop: '1px solid var(--border)',
            }}>2026-05-19 · {hour}</div>
            {items.map((a) => (
              <div key={a.id} onClick={() => setOpenId(a.id)} style={{
                padding: '12px 20px',
                borderBottom: '1px solid var(--border)',
                display:'flex', alignItems:'center', gap: 14,
                cursor:'pointer',
                transition: 'background .08s',
              }}
              onMouseEnter={(e) => e.currentTarget.style.background = 'var(--surface-2)'}
              onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
                <span className="mono" style={{ fontSize: 12, color: 'var(--text-3)', width: 72 }}>{a.at}</span>
                <span style={{
                  width: 8, height: 8, borderRadius: '50%',
                  background: a.actor === 'system' ? 'var(--info)' : 'var(--indigo)',
                }}/>
                <span className="mono" style={{ fontSize: 12, color: 'var(--text-2)', width: 80 }}>{a.actor}</span>
                <span className="mono" style={{
                  fontSize: 11, padding: '2px 6px',
                  background: 'var(--surface-3)',
                  borderRadius: 'var(--r-sm)',
                  color: 'var(--text-2)',
                  whiteSpace:'nowrap',
                }}>{a.action}</span>
                <span style={{ flex: 1, fontSize: 13 }}>{a.desc}</span>
                <span className="mono" style={{ fontSize: 11, color: 'var(--text-3)' }}>{a.target}</span>
                {a.action.includes('edit') || a.action.includes('add') || a.action.includes('delete') ? (
                  <window.Btn size="xs">看 diff</window.Btn>
                ) : (
                  <span style={{ width: 64 }}/>
                )}
              </div>
            ))}
          </div>
        ))}
      </div>

      {open && (
        <>
          <div className="drawer-backdrop" onClick={() => setOpenId(null)}/>
          <div className="drawer">
            <AuditDetail a={open} onClose={() => setOpenId(null)}/>
          </div>
        </>
      )}
    </div>
  );
}

function AuditDetail({ a, onClose }) {
  return (
    <>
      <div className="drawer-header">
        <div>
          <div style={{ fontWeight: 600, fontSize: 15 }}>{a.desc}</div>
          <div className="muted" style={{ fontSize: 12, marginTop: 6 }}>
            <span className="mono">{a.at}</span> · {a.actor} · <span className="mono">{a.action}</span>
          </div>
        </div>
        <button className="icon-btn" onClick={onClose}><window.I.x size={16}/></button>
      </div>
      <div className="drawer-body">
        <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap: 12 }}>
          <Meta label="目标">{a.target}</Meta>
          <Meta label="操作者">{a.actor}</Meta>
          <Meta label="时间" mono>2026-05-19 {a.at}</Meta>
          <Meta label="IP" mono>10.0.0.4</Meta>
        </div>

        <div className="divider"/>
        <div className="muted" style={{ fontSize: 12, marginBottom: 8 }}>变更 Diff</div>
        <DiffView/>

        <div className="divider"/>
        <div className="muted" style={{ fontSize: 12, marginBottom: 8 }}>关联</div>
        <div style={{ display:'flex', gap: 8, flexWrap:'wrap' }}>
          <window.Btn size="sm">跳转到目标</window.Btn>
          <window.Btn size="sm">查看提交者</window.Btn>
          <window.Btn size="sm">复制 JSON</window.Btn>
        </div>
      </div>
      <div className="drawer-footer">
        <span className="muted" style={{ fontSize: 12 }}>该操作不可撤销</span>
        <window.Btn onClick={onClose}>关闭</window.Btn>
      </div>
    </>
  );
}

function DiffView() {
  return (
    <div style={{
      border: '1px solid var(--border)',
      borderRadius: 'var(--r-md)',
      overflow: 'hidden',
      fontFamily: 'var(--font-mono)',
      fontSize: 12,
    }}>
      <div style={{ display:'grid', gridTemplateColumns: '1fr 1fr', borderBottom: '1px solid var(--border)' }}>
        <div style={{ padding: '6px 10px', background: 'var(--surface-2)', borderRight: '1px solid var(--border)', fontWeight: 500 }}>
          <span style={{ color: 'var(--danger)' }}>−</span> 修改前
        </div>
        <div style={{ padding: '6px 10px', background: 'var(--surface-2)', fontWeight: 500 }}>
          <span style={{ color: 'var(--success)' }}>+</span> 修改后
        </div>
      </div>
      {[
        ['"weather": "晴天"', '"weather": "雨天"'],
        ['"speed": "S+"',    '"speed": "S+"'],
        ['"playerList": [',  '"playerList": ['],
        ['  ...3 unchanged', '  ...3 unchanged'],
        ['  "可达鸭"',       '  "钢铁人"'],
        ['  ...2 unchanged', '  ...2 unchanged'],
        [']',                 ']'],
      ].map(([l, r], i) => {
        const changed = l !== r;
        return (
          <div key={i} style={{ display:'grid', gridTemplateColumns: '1fr 1fr' }}>
            <div style={{
              padding: '4px 10px',
              background: changed ? 'rgba(239,68,68,.06)' : 'transparent',
              borderRight: '1px solid var(--border)',
              color: changed ? 'var(--danger)' : 'var(--text-2)',
            }}>{l}</div>
            <div style={{
              padding: '4px 10px',
              background: changed ? 'rgba(16,185,129,.08)' : 'transparent',
              color: changed ? 'var(--success)' : 'var(--text-2)',
            }}>{r}</div>
          </div>
        );
      })}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────
// Stub pages: Scrape, Feedback, Status, Settings

function ScrapePage({ toast }) {
  const [items, setItems] = React.useState([]);
  const reload = React.useCallback(() => {
    window.API.scrapeHistory().then((r) => setItems(r?.items || [])).catch(() => {});
  }, []);
  React.useEffect(reload, [reload]);
  const trigger = () => {
    if (!confirm('立即触发抓取?预计 2-5 分钟。')) return;
    window.API.triggerScrape().then(() => { toast?.push?.('已触发'); reload(); })
      .catch((e) => toast?.push?.(`失败:${e.message}`, 'err'));
  };
  const rows = items.length ? items.map((r) => ({
    t: new Date(r.triggered_at).toLocaleString('zh-CN', { hour12: false }),
    src: r.trigger_source, status: r.status,
    change: r.diff_summary || '—',
    dur: r.duration_ms ? `${Math.round(r.duration_ms/1000)}s` : '—',
    url: r.github_run_url,
  })) : [
    { t:'(暂无数据)', src:'—', status:'—', change:'—', dur:'—' },
  ];
  return <StubPage title="抓取控制台" subtitle="任务历史 / 手动触发 / 计划编辑"
                   actions={<window.Btn kind="primary" icon={<window.I.refresh size={14}/>} onClick={trigger}>立即触发</window.Btn>}>
    <div className="card" style={{ padding: 0 }}>
      <table className="table">
        <thead><tr>
          <th>时间</th><th>触发</th><th>状态</th><th>变更</th><th>耗时</th><th></th>
        </tr></thead>
        <tbody>
          {rows.map((r, i) => (
            <tr key={i}>
              <td className="mono">{r.t}</td>
              <td className="muted">{r.src}</td>
              <td><window.Pill status={r.status}/></td>
              <td className="mono">{r.change}</td>
              <td className="mono">{r.dur}</td>
              <td style={{ textAlign:'right' }}>{r.url ? <a className="btn xs" href={r.url} target="_blank" rel="noreferrer">详情</a> : <window.Btn size="xs" disabled>详情</window.Btn>}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  </StubPage>;
}

function FeedbackPage() {
  const [items, setItems] = React.useState(window.MOCK.FEEDBACK);
  React.useEffect(() => {
    window.API.feedback().then((r) => {
      if (r?.items) {
        setItems(r.items.map((x) => ({
          id: 'f' + x.id, _id: x.id, kind: x.kind, title: x.title, content: x.content,
          status: x.status,
          at: x.created_at ? window.relTime(new Date(x.created_at).toISOString()) : '',
        })));
      }
    }).catch(() => {});
  }, []);
  return <StubPage title="用户反馈" subtitle="App 用户提交的 bug / 建议 / 内容纠错">
    {items.length === 0 ? (
      <window.Empty title="暂无反馈" hint="App 用户提交后会出现在这里"/>
    ) : items.map((f) => (
      <div key={f.id} className="card card-pad-sm" style={{ marginBottom: 8 }}>
        <div className="hstack">
          <Chip>{f.kind}</Chip>
          <window.Pill status={f.status === 'new' ? 'new' : 'pending'}>{f.status}</window.Pill>
          <span style={{ fontWeight: 500, flex: 1 }}>{f.title}</span>
          <span className="muted" style={{ fontSize: 12 }}>{f.at}</span>
          <window.Btn size="xs" onClick={() => {
            window.API.feedbackUpdate(f._id, { status: 'reviewing' }).then(() =>
              setItems((arr) => arr.map((x) => x.id === f.id ? { ...x, status: 'reviewing' } : x))
            );
          }}>处理</window.Btn>
        </div>
        {f.content && (
          <div className="muted" style={{ fontSize: 12, marginTop: 6 }}>{f.content}</div>
        )}
      </div>
    ))}
  </StubPage>;
}

function StatusPage() {
  const [data, setData] = React.useState(null);
  const [manifest, setManifest] = React.useState(null);
  React.useEffect(() => {
    window.API.status().then(setData).catch(() => {});
    window.API.manifest().then(setManifest).catch(() => {});
  }, []);
  const subSum = data?.submissions
    ? Object.values(data.submissions).reduce((a, b) => a + b, 0)
    : 0;
  return <StubPage title="系统状态" subtitle="manifest / 数据规模 / Cloudflare 额度 / 部署历史">
    <div style={{ display:'grid', gridTemplateColumns:'repeat(4, 1fr)', gap: 12, marginBottom: 16 }}>
      <Kpi2 label="manifest" value={manifest ? `v${manifest.version}` : '—'} foot={manifest?.updatedAt || ''}/>
      <Kpi2 label="总提交" value={subSum}/>
      <Kpi2 label="待审" value={data?.submissions?.pending ?? 0}/>
      <Kpi2 label="D1 行数" value={data?.d1_total_rows ?? 0}/>
    </div>
    <div className="card card-pad">
      <div className="card-title" style={{ marginBottom: 16 }}>最近 7 天提交</div>
      <SevenDayChart data={data?.submissions_7d || [0,0,0,0,0,0,0]}/>
    </div>
  </StubPage>;
}

function SevenDayChart({ data }) {
  const max = Math.max(1, ...data);
  const days = ['7d 前','6d 前','5d 前','4d 前','3d 前','昨日','今日'];
  return (
    <div style={{ display:'flex', alignItems:'flex-end', gap: 12, height: 120 }}>
      {data.map((v, i) => (
        <div key={i} style={{ flex: 1, display:'flex', flexDirection:'column', alignItems:'center', height:'100%' }}>
          <div className="mono" style={{ fontSize: 11, color: 'var(--text-3)', marginBottom: 4 }}>{v}</div>
          <div style={{ width: '100%', height: `${(v/max)*100}%`, background: 'var(--indigo)', borderRadius: '4px 4px 0 0', minHeight: 2 }}/>
          <div className="mono" style={{ fontSize: 10, color: 'var(--text-3)', marginTop: 6 }}>{days[i]}</div>
        </div>
      ))}
    </div>
  );
}

function SettingsPage_OLD_UNUSED() { return null; }

function Kpi2({ label, value, foot }) {
  return (
    <div className="card kpi" style={{ minHeight: 88 }}>
      <div className="kpi-label">{label}</div>
      <div className="kpi-value">{value}</div>
      {foot && <div className="kpi-foot"><span>{foot}</span></div>}
    </div>
  );
}

function SettingRow({ label, value, mono }) {
  return (
    <div style={{ padding: '10px 12px', background: 'var(--surface-2)', border:'1px solid var(--border)', borderRadius:'var(--r-md)' }}>
      <div className="muted" style={{ fontSize: 11 }}>{label}</div>
      <div className={mono ? 'mono' : ''} style={{ fontWeight: 500, marginTop: 4 }}>{value}</div>
    </div>
  );
}

function FakeChart() {
  const data = Array.from({ length: 30 }, (_, i) => 2 + Math.round(Math.sin(i/3) * 4 + 5 + (i % 7 === 0 ? 4 : 0)));
  const max = Math.max(...data);
  return (
    <div>
      <div style={{ display:'flex', alignItems:'flex-end', gap: 4, height: 140 }}>
        {data.map((v, i) => (
          <div key={i} style={{
            flex: 1,
            height: `${(v / max) * 100}%`,
            background: i > 26 ? 'var(--indigo)' : 'var(--primary-200)',
            borderRadius: 3,
            transition: 'height .3s',
          }} title={`Day ${i + 1}: ${v}`}/>
        ))}
      </div>
      <div className="hstack" style={{ marginTop: 8, justifyContent:'space-between', color: 'var(--text-3)', fontSize: 11, fontFamily:'var(--font-mono)' }}>
        <span>30 天前</span><span>今天</span>
      </div>
    </div>
  );
}

function StubPage({ title, subtitle, children, actions }) {
  return (
    <div>
      <div className="page-header">
        <div>
          <div className="breadcrumb"><span>系统</span><span className="sep">/</span><span>{title}</span></div>
          <h1 className="page-title">{title}</h1>
          <div className="page-subtitle">{subtitle}</div>
        </div>
        {actions && <div className="hstack">{actions}</div>}
      </div>
      {children}
    </div>
  );
}

// chip helper reused
function Chip({ children }) {
  return (
    <span style={{
      display:'inline-flex', alignItems:'center',
      fontSize: 11, padding: '2px 7px',
      background: 'var(--surface-2)', border: '1px solid var(--border)',
      borderRadius: 'var(--r-sm)',
      color: 'var(--text-2)', whiteSpace:'nowrap',
    }}>{children}</span>
  );
}

Object.assign(window, {
  PokedexPage, WikiPage, AuditLogPage,
  ScrapePage, FeedbackPage, StatusPage,
});
