/* ComponentsPage.jsx — component documentation manager (bilingual EN / 中文).
   EDITABLE article layer (summary/status/overview/usage/examples) is separated from
   READ-ONLY layers (props / changelog / tokens-used, from code). The editor is
   language-aware: in 中文 mode the editable fields bind to the *Zh variants, so the
   toggle drives both reading and writing. Saving a draft pushes a change into Changes. */
const { useState: useStateCP, useMemo: useMemoCP } = React;

/* seed one sample interaction note (demo of the uploaded-Markdown rendering) */
if (typeof COMPONENT_MAP !== 'undefined' && COMPONENT_MAP.button && !COMPONENT_MAP.button.interaction) {
  COMPONENT_MAP.button.interaction = [
    '## 设计思路 · Button',
    '',
    '主按钮承载页面中**最主要的单一动作**。每个视图建议只保留一个 Primary 按钮,其余动作用 Secondary / Ghost。',
    '',
    '### 状态流转',
    '- **默认 → 悬停**:背景提亮 8%,`120ms` 过渡',
    '- **悬停 → 按下**:背景压暗,`scale(0.98)`',
    '- **加载中**:展示 spinner 并禁用,避免重复提交',
    '',
    '### 键盘与无障碍',
    '- `Enter` / `Space` 触发',
    '- 焦点态显示 2px 强调色描边(`focus-ring`)',
    '- 加载态设置 `aria-busy="true"`',
    '',
    '> 删除等危险操作使用 **Danger** 变体,并建议二次确认。',
    '',
    '*(本说明为示例:在 Obsidian 写好后,选中 .md 与图片一起上传即可,图片会自动内联。)*',
  ].join('\n');
}

/* ---- component status pill (bilingual) ---- */
function CompStatusTag({ status, size = 'md' }) {
  const m = COMP_STATUS[status] || COMP_STATUS.stable;
  const h = size === 'sm' ? 'h-[18px] text-[10.5px]' : 'h-[20px] text-[11px]';
  return (
    <span className={cx('inline-flex items-center gap-1 px-1.5 rounded-full font-medium tracking-wide', h)}
      style={{ background: 'color-mix(in srgb, ' + m.color + ' 13%, transparent)', color: m.color }}>
      <span className="w-1.5 h-1.5 rounded-full" style={{ background: m.color }} />{tr(m.label, m.labelZh)}
    </span>
  );
}

/* ---- caution badge (independent flag, orange) ---- */
function CautionBadge({ size = 'md' }) {
  const h = size === 'sm' ? 'h-[18px] text-[10.5px]' : 'h-[20px] text-[11px]';
  return (
    <span className={cx('inline-flex items-center gap-1 px-1.5 rounded-full font-medium', h)} title="Caution"
      style={{ background: 'color-mix(in srgb, var(--warning) 14%, transparent)', color: 'var(--warning)' }}>
      <Icon name="warningTri" size={size === 'sm' ? 10 : 11} />{tr('Caution', '谨慎')}
    </span>
  );
}

/* ---- small switch ---- */
function Toggle({ on, onChange, tone = 'accent' }) {
  const bg = on ? (tone === 'warning' ? 'var(--warning)' : 'var(--accent)') : 'var(--border-strong)';
  return (
    <button onClick={() => onChange(!on)} role="switch" aria-checked={on}
      className="relative inline-flex items-center h-[22px] w-[38px] rounded-full transition-colors focus-ring shrink-0" style={{ background: bg }}>
      <span className={cx('inline-block w-[18px] h-[18px] rounded-full bg-white shadow-[0_1px_2px_rgba(0,0,0,0.25)] transition-transform', on ? 'translate-x-[18px]' : 'translate-x-[2px]')} />
    </button>
  );
}

/* ---- section shell with an honest source badge ---- */
function CompSection({ icon, title, source, note, action, children }) {
  const badge = source === 'editable' ? { label: tr('Editable', '可编辑'), cls: 'text-accent', bg: 'var(--accent-weak)' }
    : source === 'code' ? { label: tr('From code', '来自代码'), cls: 'text-ink3', bg: 'var(--bg-sunken)' }
    : source === 'auto' ? { label: tr('Auto-generated', '自动生成'), cls: 'text-ink3', bg: 'var(--bg-sunken)' }
    : null;
  return (
    <section className="rounded-[12px] border border-line bg-panel overflow-hidden">
      <div className="flex items-center gap-2 px-4 h-11 border-b border-line bg-sunken">
        <Icon name={icon} size={15} className="text-ink2" />
        <span className="text-[13px] font-semibold text-ink">{title}</span>
        {badge && <span className={cx('inline-flex items-center h-[18px] px-1.5 rounded-full text-[10px] font-medium uppercase tracking-wide border border-line', badge.cls)} style={{ background: badge.bg }}>{badge.label}</span>}
        <div className="ml-auto">{action}</div>
      </div>
      {note && <div className="px-4 pt-2.5 text-[11.5px] text-ink3">{note}</div>}
      <div className="p-4">{children}</div>
    </section>
  );
}

/* ---- editable do / don't list ---- */
function EditableList({ items, onChange, tone }) {
  return (
    <div className="flex flex-col gap-1.5">
      {items.map((it, i) => (
        <div key={i} className="flex items-center gap-2 group">
          <span className="shrink-0 w-4 flex justify-center">
            <Icon name={tone === 'do' ? 'check' : 'close'} size={13} className={tone === 'do' ? 'text-success' : 'text-danger'} />
          </span>
          <input value={it} onChange={e => { const n = items.slice(); n[i] = e.target.value; onChange(n); }}
            className="flex-1 h-8 bg-sunken border border-line rounded-[7px] px-2.5 text-[12.5px] text-ink focus-ring focus:border-line2" />
          <button onClick={() => onChange(items.filter((_, j) => j !== i))} title="Remove"
            className="w-7 h-7 rounded-[7px] flex items-center justify-center text-ink3 opacity-0 group-hover:opacity-100 hover:text-danger hover:bg-hover transition"><Icon name="close" size={13} /></button>
        </div>
      ))}
      <button onClick={() => onChange([...items, ''])}
        className="self-start inline-flex items-center gap-1.5 h-7 px-2 rounded-[7px] text-[12px] text-ink2 hover:text-ink hover:bg-hover transition"><Icon name="plus" size={13} />{tr('Add', '添加')}</button>
    </div>
  );
}

/* ---- live demo for the wired components; placeholder otherwise (matched by id prefix) ---- */
function DemoPreview({ demo }) {
  const k = (demo || '').split('-')[0];
  if (k === 'button') return <div className="flex items-center gap-2 flex-wrap">{['primary', 'secondary', 'ghost', 'danger'].map(v => <Button key={v} variant={v} size="sm">{v[0].toUpperCase() + v.slice(1)}</Button>)}</div>;
  if (k === 'link') return <span className="text-accent text-[13px] hover:underline cursor-pointer inline-flex items-center gap-1"><Icon name="link" size={13} />Open documentation</span>;
  if (k === 'input') return <div className="w-56"><TextInput value="Acme Industrial" onChange={() => {}} /></div>;
  if (k === 'search') return <div className="w-56"><SearchInput value="" onChange={() => {}} placeholder={tr('Search…', '搜索…')} size="sm" /></div>;
  if (k === 'switch') return <div className="flex items-center gap-3"><Toggle on={true} onChange={() => {}} /><Toggle on={false} onChange={() => {}} /></div>;
  if (k === 'tag') return <div className="flex items-center gap-1.5">{['Stable', 'Beta', 'HMI'].map(t => <span key={t} className="inline-flex items-center h-[20px] px-2 rounded-[4px] bg-sunken border border-line text-[11.5px] text-ink2">{t}</span>)}</div>;
  if (k === 'badge') return <span className="relative inline-flex"><Icon name="bell" size={22} className="text-ink2" /><span className="absolute -top-1 -right-1.5 min-w-[16px] h-4 px-1 rounded-full text-[10px] font-semibold text-white flex items-center justify-center" style={{ background: 'var(--danger)' }}>5</span></span>;
  if (k === 'tabs') return <div className="flex items-center gap-4 border-b border-line text-[12.5px] w-full"><span className="pb-1.5 -mb-px border-b-2 text-accent font-medium" style={{ borderColor: 'var(--accent)' }}>Overview</span><span className="pb-1.5 text-ink2">Specs</span><span className="pb-1.5 text-ink2">History</span></div>;
  if (k === 'table') return <div className="w-full rounded-[4px] border border-line overflow-hidden text-[11.5px]"><div className="grid grid-cols-3 bg-sunken text-ink3 font-medium"><span className="px-2 py-1">Sensor</span><span className="px-2 py-1">Value</span><span className="px-2 py-1">Status</span></div>{[['Pressure', '2.4 MPa', 'OK'], ['Temp', '78 °C', 'OK']].map((row, i) => <div key={i} className="grid grid-cols-3 border-t border-line">{row.map((cell, j) => <span key={j} className="px-2 py-1 text-ink2">{cell}</span>)}</div>)}</div>;
  if (k === 'modal') return <div className="rounded-[12px] border border-line bg-panel shadow-[0_8px_28px_rgba(0,0,0,0.14)] w-56 overflow-hidden"><div className="px-3 py-2 text-[12.5px] font-medium text-ink border-b border-line">Confirm run</div><div className="px-3 py-2 text-[11.5px] text-ink2">Start the diagnostic cycle?</div><div className="px-3 py-2 flex justify-end gap-2"><Button size="sm" variant="ghost">Cancel</Button><Button size="sm" variant="primary">Run</Button></div></div>;
  if (k === 'drawer') return <div className="relative w-full h-20 rounded-[8px] bg-sunken border border-line overflow-hidden"><div className="absolute top-0 right-0 h-full w-2/3 bg-panel border-l border-line p-2.5 text-[11.5px] text-ink2" style={{ boxShadow: '-8px 0 16px rgba(0,0,0,0.08)' }}>Side drawer panel</div></div>;
  if (k === 'progress') return <div className="w-56 flex flex-col gap-2">{[68, 34].map((w, i) => <div key={i} className="h-1.5 rounded-full bg-sunken overflow-hidden"><div className="h-full rounded-full" style={{ width: w + '%', background: 'var(--accent)' }} /></div>)}</div>;
  return <div className="h-16 rounded-[8px] bg-sunken border border-dashed border-line flex items-center justify-center text-[11.5px] text-ink3 font-mono">{demo}</div>;
}

/* ---- list card ---- */
function ComponentCard({ c, onClick }) {
  const tk = tokensUsedBy(c.id);
  return (
    <button onClick={onClick}
      className={cx('text-left rounded-[12px] border border-line bg-panel p-4 hover:border-line2 hover:bg-hover transition group flex flex-col gap-3', c.status === 'deprecated' && 'opacity-60')}>
      <div className="flex items-start gap-3">
        <span className="w-9 h-9 rounded-[8px] bg-sunken border border-line flex items-center justify-center text-ink2 shrink-0"><Icon name={c.icon} size={18} /></span>
        <div className="min-w-0 flex-1">
          <div className="flex items-center gap-1.5">
            <span className="text-[14px] font-semibold text-ink truncate">{tr(c.name, c.nameZh)}</span>
            {c.hasDraft && <span className="w-1.5 h-1.5 rounded-full bg-accent shrink-0" title="Unsaved draft" />}
          </div>
          <div className="text-[11.5px] text-ink3">{tr(c.category, c.categoryZh)}</div>
        </div>
        <div className="flex items-center gap-1 shrink-0">
          {c.caution && <CautionBadge size="sm" />}
          <CompStatusTag status={c.status} size="sm" />
        </div>
      </div>
      <p className="text-[12px] text-ink2 leading-snug line-clamp-2">{tr(c.summary, c.summaryZh)}</p>
      <div className="flex items-center gap-3 text-[11px] text-ink3 mt-auto pt-1">
        <span className="font-mono">v{c.version}</span>
        <span className="inline-flex items-center gap-1"><Icon name="link" size={11} />{tk.length} {tr('tokens', '个 token')}</span>
        <span className="ml-auto inline-flex items-center gap-1 opacity-0 group-hover:opacity-100 transition text-accent">{tr('Open', '打开')}<Icon name="chevronR" size={12} /></span>
      </div>
    </button>
  );
}

/* ---- list view ---- */
function ComponentsList({ onSelect }) {
  const [q, setQ] = useStateCP('');
  const [cat, setCat] = useStateCP('all');
  const [status, setStatus] = useStateCP('all');
  const [draftsOnly, setDraftsOnly] = useStateCP(false);
  const filtered = useMemoCP(() => COMPONENTS.filter(c => {
    if (draftsOnly && !c.hasDraft) return false;
    if (cat !== 'all' && c.category !== cat) return false;
    if (status !== 'all' && c.status !== status) return false;
    if (q) {
      const s = q.toLowerCase();
      const hay = [c.name, c.nameZh, c.summary, c.summaryZh, c.category, c.categoryZh, (c.tags || []).join(' ')].join(' ').toLowerCase();
      if (!hay.includes(s)) return false;
    }
    return true;
  }), [q, cat, status, draftsOnly]);
  const draftCount = COMPONENTS.filter(c => c.hasDraft).length;
  return (
    <div className="h-full flex flex-col">
      <PageToolbar
        title={tr('Components', '组件')}
        count={COMPONENTS.length}
        titleMeta={draftCount > 0 ? (
          <button onClick={() => setDraftsOnly(d => !d)} title={tr('Show only components with unsaved drafts', '只看有未保存草稿的组件')}
            className={cx('text-[13px] whitespace-nowrap transition', draftsOnly ? 'text-accent font-medium' : 'text-ink3 hover:text-ink2')}>
            <span className="inline-block w-1.5 h-1.5 rounded-full bg-accent align-middle mr-2" />{draftCount} {tr('with drafts', '含草稿')}
          </button>
        ) : null}
        search={{ value:q, onChange:setQ, placeholder:tr('Search components…', '搜索组件…') }}
        categories={{ value:cat, onChange:setCat, options:COMP_CATEGORIES.map(v=>({ value:v, label:tr(v, COMP_CAT_LABELS[v]) })) }}
        facets={
          <Segmented size="sm" value={status} onChange={setStatus} options={[{ value: 'all', label: tr('Any', '全部') }, { value: 'stable', label: tr('Stable', '稳定') }, { value: 'beta', label: tr('Beta', '测试版') }, { value: 'deprecated', label: tr('Deprecated', '已废弃') }]} />
        }
      />
      <div className="flex-1 overflow-y-auto ds-scroll p-6">
        {filtered.length === 0 ? (
          <EmptyState icon="component" title={tr('No components match', '没有匹配的组件')} body={tr('Try clearing filters or adjusting your search.', '试试清除筛选或调整搜索。')}
            action={<Button variant="secondary" onClick={() => { setQ(''); setCat('all'); setStatus('all'); setDraftsOnly(false); }}>{tr('Reset', '重置')}</Button>} />
        ) : (
          <div className="grid gap-3" style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))' }}>
            {filtered.map(c => <ComponentCard key={c.id} c={c} onClick={() => onSelect(c.id)} />)}
          </div>
        )}
      </div>
    </div>
  );
}

/* ---- article editor helpers (bilingual) ---- */
function cloneArticle(c) {
  return {
    summary: c.summary, summaryZh: c.summaryZh || '',
    status: c.status, caution: !!c.caution, cautionReason: c.cautionReason || '', cautionReasonZh: c.cautionReasonZh || '',
    overview: c.overview, overviewZh: c.overviewZh || '',
    dos: c.dos.slice(), dosZh: (c.dosZh || []).slice(),
    donts: c.donts.slice(), dontsZh: (c.dontsZh || []).slice(),
    examples: c.examples.map(e => ({ ...e })),
    interaction: c.interaction || '',
  };
}
function sameArticle(a, b) { return JSON.stringify(cloneArticle(a)) === JSON.stringify(b); }
function describeChange(orig, draft) {
  if (orig.status !== draft.status) {
    return { action: draft.status === 'deprecated' ? 'deprecated' : 'modified', target: orig.name,
      diffType: 'compStatus', from: orig.status, to: draft.status,
      impact: draft.status === 'deprecated' ? 'major' : 'minor' };
  }
  if ((!!orig.caution) !== (!!draft.caution) || (orig.cautionReason || '') !== (draft.cautionReason || '') || (orig.cautionReasonZh || '') !== (draft.cautionReasonZh || '')) {
    return { action: 'modified', target: orig.name, diffType: 'compCaution',
      on: !!draft.caution, note: draft.caution ? draft.cautionReason : undefined, impact: 'minor' };
  }
  const parts = [];
  if (orig.overview !== draft.overview || orig.overviewZh !== draft.overviewZh) parts.push('overview');
  if (orig.summary !== draft.summary || orig.summaryZh !== draft.summaryZh) parts.push('summary');
  if (JSON.stringify([orig.dos, orig.dosZh, orig.donts, orig.dontsZh]) !== JSON.stringify([draft.dos, draft.dosZh, draft.donts, draft.dontsZh])) parts.push('usage');
  if (JSON.stringify(orig.examples.map(e => ({ s: e.show, n: e.note, z: e.noteZh }))) !== JSON.stringify(draft.examples.map(e => ({ s: e.show, n: e.note, z: e.noteZh })))) parts.push('examples');
  if ((orig.interaction || '') !== (draft.interaction || '')) parts.push('interaction notes');
  return { action: 'modified', target: orig.name, diffType: 'compDocs', summary: 'Edited ' + (parts.join(', ') || 'docs'), impact: 'patch' };
}

/* ---- detail / article editor ---- */
function ComponentDetail({ id, ctx, onBack, onSave, onViewInGraph }) {
  const original = COMPONENT_MAP[id];
  const [draft, setDraft] = useStateCP(() => cloneArticle(original));
  const set = (k, v) => setDraft(d => ({ ...d, [k]: v }));
  const setExample = (i, k, v) => setDraft(d => ({ ...d, examples: d.examples.map((e, j) => j === i ? { ...e, [k]: v } : e) }));
  const dirty = !sameArticle(original, draft);
  const usedTokens = useMemoCP(() => tokensUsedBy(id), [id]);

  const zh = getAppLang() === 'zh';
  const F = (b) => zh ? b + 'Zh' : b;                 // editable field for the current language
  const reasonVal = draft[F('cautionReason')] || '';
  const cautionInvalid = draft.caution && !reasonVal.trim();

  function save() { onSave(id, draft, describeChange(original, draft)); }
  async function onMd(e) { const md = await processMdFiles(e.target.files); e.target.value = ''; if (md != null) set('interaction', md); }

  return (
    <div className="h-full flex flex-col">
      {/* sub-header */}
      <div className="shrink-0 border-b border-line px-6 h-[60px] flex items-center gap-3 bg-panel">
        <button onClick={onBack} className="inline-flex items-center gap-1 h-8 px-2 rounded-[8px] text-[13px] text-ink2 hover:text-ink hover:bg-hover transition shrink-0">
          <Icon name="chevronR" size={15} className="rotate-180" />{tr('Components', '组件')}
        </button>
        <span className="w-px h-5 bg-line shrink-0" />
        <span className="w-8 h-8 rounded-[8px] bg-sunken border border-line flex items-center justify-center text-ink2 shrink-0"><Icon name={original.icon} size={17} /></span>
        <div className="min-w-0">
          <div className="flex items-center gap-2">
            <h1 className="text-[16px] font-semibold text-ink truncate">{tr(original.name, original.nameZh)}</h1>
            <CompStatusTag status={draft.status} size="sm" />
            {draft.caution && <CautionBadge size="sm" />}
          </div>
          <div className="text-[11.5px] text-ink3 font-mono truncate">{original.repo} · v{original.version}</div>
        </div>
        <div className="ml-auto flex items-center gap-2.5 shrink-0">
          {dirty && <span className="text-[12px] text-accent inline-flex items-center gap-1.5"><span className="w-1.5 h-1.5 rounded-full bg-accent" />{tr('Unsaved', '未保存')}</span>}
          <Button variant="primary" icon="check" disabled={!dirty || cautionInvalid || !canEdit()} onClick={save}>{tr('Save draft', '保存草稿')}</Button>
        </div>
      </div>

      {/* body */}
      <div className="flex-1 overflow-y-auto ds-scroll">
        <div className="max-w-[880px] mx-auto px-6 py-6 flex flex-col gap-5">

          {draft.caution && reasonVal.trim() && (
            <div className="flex items-start gap-2.5 rounded-[12px] border px-3.5 py-3 text-[12.5px]" style={{ borderColor: 'color-mix(in srgb, var(--warning) 35%, transparent)', background: 'color-mix(in srgb, var(--warning) 8%, transparent)' }}>
              <Icon name="warningTri" size={16} className="shrink-0 mt-0.5" style={{ color: 'var(--warning)' }} />
              <div><span className="font-semibold" style={{ color: 'var(--warning)' }}>{tr('Caution · edit with care. ', '谨慎 · 改动需当心。')}</span><span className="text-ink2">{tr(draft.cautionReason, draft.cautionReasonZh)}</span></div>
            </div>
          )}

          {/* Basics — editable */}
          <CompSection icon="info" title={tr('Basics', '基本信息')} source="editable">
            <div className="flex flex-col gap-4">
              <div>
                <label className="text-[12px] font-medium text-ink2 block mb-1.5">{tr('Summary', '简介')}</label>
                <TextInput value={draft[F('summary')]} onChange={e => set(F('summary'), e.target.value)} placeholder={tr('One-line description', '一句话描述')} />
              </div>
              <div className="flex flex-wrap items-end gap-6">
                <div>
                  <label className="text-[12px] font-medium text-ink2 block mb-1.5">{tr('Status', '状态')}</label>
                  <Segmented size="sm" value={draft.status} onChange={v => set('status', v)}
                    options={[{ value: 'stable', label: tr('Stable', '稳定') }, { value: 'beta', label: tr('Beta', '测试版') }, { value: 'deprecated', label: tr('Deprecated', '已废弃') }]} />
                </div>
                <div className="min-w-0">
                  <label className="text-[12px] font-medium text-ink2 block mb-1.5">{tr('Tags', '标签')}</label>
                  <div className="flex items-center gap-1.5 flex-wrap h-8">
                    {original.tags.map(t => <span key={t} className="inline-flex items-center h-[22px] px-2 rounded-[4px] bg-sunken border border-line text-[11.5px] text-ink2 font-mono">{t}</span>)}
                  </div>
                </div>
              </div>
              <div>
                <label className="text-[12px] font-medium text-ink2 block mb-2">{tr('Caution flag', '谨慎标记')}</label>
                <div className="flex items-center gap-3">
                  <Toggle on={draft.caution} tone="warning" onChange={v => set('caution', v)} />
                  <span className="text-[12px] text-ink2">{tr('Editing this component may ripple to dependent products', '改动此组件可能波及依赖它的产品')}</span>
                </div>
                {draft.caution && (
                  <div className="mt-3">
                    <label className="text-[12px] font-medium text-ink2 mb-1.5 flex items-center gap-1.5"><Icon name="warningTri" size={13} style={{ color: 'var(--warning)' }} />{tr('Why caution?', '为什么谨慎?')}<span className="text-danger">*</span></label>
                    <textarea value={reasonVal} onChange={e => set(F('cautionReason'), e.target.value)} rows={2}
                      placeholder={tr('Explain the downstream impact reviewers should know about…', '说明评审者需要知道的下游影响…')}
                      className="w-full bg-sunken border border-line rounded-[8px] px-3 py-2 text-[12.5px] text-ink focus-ring focus:border-line2 resize-y ds-scroll" />
                    {!reasonVal.trim() && <p className="text-[11.5px] text-danger mt-1">{tr('A reason is required when caution is on.', '开启谨慎标记时必须填写原因。')}</p>}
                  </div>
                )}
              </div>
            </div>
          </CompSection>

          {/* Overview — editable */}
          <CompSection icon="edit" title={tr('Overview', '概述')} source="editable">
            <textarea value={draft[F('overview')]} onChange={e => set(F('overview'), e.target.value)} rows={4}
              className="w-full bg-sunken border border-line rounded-[8px] px-3 py-2.5 text-[13px] leading-relaxed text-ink focus-ring focus:border-line2 resize-y ds-scroll" />
          </CompSection>

          {/* Usage — editable */}
          <CompSection icon="check" title={tr('Usage guidelines', '使用规范')} source="editable">
            <div className="grid gap-6" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))' }}>
              <div>
                <div className="flex items-center gap-1.5 mb-2 text-[12px] font-semibold text-success"><Icon name="check" size={14} />{tr('Do', '推荐')}</div>
                <EditableList items={draft[F('dos')]} onChange={v => set(F('dos'), v)} tone="do" />
              </div>
              <div>
                <div className="flex items-center gap-1.5 mb-2 text-[12px] font-semibold text-danger"><Icon name="close" size={14} />{tr("Don't", '避免')}</div>
                <EditableList items={draft[F('donts')]} onChange={v => set(F('donts'), v)} tone="dont" />
              </div>
            </div>
          </CompSection>

          {/* Interaction notes — upload a Markdown file (+ images), no editor */}
          <CompSection icon="edit" title={tr('Interaction notes', '交互说明')} source="editable"
            note={tr('Upload a Markdown file and any images it references (e.g. exported from Obsidian). Images are inlined — no editor, no zip.', '上传一个 Markdown 文件及其引用的图片(例如从 Obsidian 导出)。图片会内联进来 —— 无需在线编辑器,也不用压缩包。')}>
            <div className="flex flex-col gap-3">
              <div className="flex items-center gap-3">
                <label className={cx('inline-flex items-center gap-1.5 h-8 px-3 rounded-[8px] text-[13px] font-medium border border-line cursor-pointer hover:bg-hover transition', !canEdit() && 'opacity-40 pointer-events-none')}>
                  <Icon name="upload" size={15} />{tr('Upload .md + images', '上传 .md + 图片')}
                  <input type="file" accept=".md,.markdown,image/*,video/*,.pdf,.doc,.docx,.ppt,.pptx" multiple className="hidden" onChange={onMd} />
                </label>
                {draft.interaction && <button onClick={() => set('interaction', '')} className="text-[12.5px] text-ink3 hover:text-danger transition">{tr('Clear', '清除')}</button>}
              </div>
              {draft.interaction
                ? <div className="rounded-[12px] border border-line bg-sunken p-4 max-h-[420px] overflow-y-auto ds-scroll"><MarkdownDoc md={draft.interaction} /></div>
                : <div className="rounded-[12px] border border-dashed border-line2 bg-sunken p-6 text-center text-[12.5px] text-ink3">{tr('No interaction notes yet. Select your .md file together with any images it references.', '暂无交互说明。选择你的 .md 文件以及它引用的图片一起上传。')}</div>}
            </div>
          </CompSection>

          {/* Examples — selection editable, code/preview from repo */}
          <CompSection icon="grid" title={tr('Examples', '示例')} source="editable" note={<span>{tr('Toggle which examples appear and edit their captions. Live previews & source come from ', '控制示例的显示与说明文字。实时预览与源码来自 ')}<span className="font-mono text-ink2">{original.repo}</span>{tr(' — not edited here.', ' —— 不在此编辑。')}</span>}>
            <div className="flex flex-col gap-3">
              {draft.examples.map((ex, i) => (
                <div key={ex.id} className={cx('rounded-[12px] border border-line overflow-hidden transition', !ex.show && 'opacity-55')}>
                  <div className="flex items-center gap-2 px-3 h-10 border-b border-line bg-sunken">
                    <span className="text-[12.5px] font-medium text-ink">{tr(ex.title, ex.titleZh)}</span>
                    <span className="font-mono text-[10.5px] text-ink3">{ex.demo}</span>
                    <button onClick={() => setExample(i, 'show', !ex.show)} title={ex.show ? 'Shown — click to hide' : 'Hidden — click to show'}
                      className={cx('ml-auto inline-flex items-center gap-1.5 h-7 px-2 rounded-[7px] text-[11.5px] font-medium transition', ex.show ? 'text-accent bg-accentWeak' : 'text-ink3 hover:text-ink hover:bg-hover')}>
                      <Icon name="eye" size={13} />{ex.show ? tr('Shown', '显示') : tr('Hidden', '隐藏')}
                    </button>
                  </div>
                  <div className="p-3.5 flex items-center justify-center bg-app/40 border-b border-line"><DemoPreview demo={ex.demo} /></div>
                  <div className="px-3 py-2">
                    <input value={ex[F('note')] || ''} onChange={e => setExample(i, F('note'), e.target.value)} placeholder={tr('Caption…', '说明…')}
                      className="w-full h-7 bg-transparent text-[12px] text-ink2 focus-ring rounded-[4px] px-1 focus:bg-sunken" />
                  </div>
                </div>
              ))}
            </div>
          </CompSection>

          {/* Props — read-only from code */}
          <CompSection icon="table" title={tr('Props / API', '属性 / API')} source="code" note={tr("Auto-extracted from the component's TypeScript types.", '从组件的 TypeScript 类型自动抽取。')}>
            <div className="overflow-x-auto ds-scroll rounded-[8px] border border-line">
              <table className="w-full text-[12.5px] border-collapse">
                <thead>
                  <tr className="bg-sunken text-ink3 text-[11px] uppercase tracking-wide">
                    <th className="text-left font-medium px-3 py-2">{tr('Prop', '属性')}</th>
                    <th className="text-left font-medium px-3 py-2">{tr('Type', '类型')}</th>
                    <th className="text-left font-medium px-3 py-2">{tr('Default', '默认值')}</th>
                    <th className="text-left font-medium px-3 py-2">{tr('Description', '说明')}</th>
                  </tr>
                </thead>
                <tbody>
                  {original.props.map(p => (
                    <tr key={p.name} className="border-t border-line align-top">
                      <td className="px-3 py-2 font-mono text-ink whitespace-nowrap">{p.name}{p.required && <span className="text-danger" title="Required">*</span>}</td>
                      <td className="px-3 py-2 font-mono text-ink2 whitespace-nowrap">{p.type}</td>
                      <td className="px-3 py-2 font-mono text-ink3 whitespace-nowrap">{p.def}</td>
                      <td className="px-3 py-2 text-ink2">{tr(p.desc, p.descZh)}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </CompSection>

          {/* Tokens used — read-only, derived from code */}
          <CompSection icon="link" title={tr('Tokens used', '使用的 token')} source="code" note={tr("Derived from the component's style references — read-only.", '从组件的样式引用派生 —— 只读。')}
            action={<Button size="sm" variant="secondary" icon="network" onClick={() => onViewInGraph(id)}>{tr('View in graph', '在关系图中查看')}</Button>}>
            {usedTokens.length === 0 ? (
              <p className="text-[12.5px] text-ink3 italic">{tr('Uses semantic tokens directly — no component-specific tokens.', '直接引用语义 token —— 没有组件专属 token。')}</p>
            ) : (
              <div className="flex flex-col">
                {usedTokens.map(t => {
                  const r = resolve(t.id, ctx);
                  return (
                    <div key={t.id} className="flex items-center gap-2.5 py-1.5 border-b border-[color:var(--border)] last:border-0">
                      <KindPreview token={t} ctx={ctx} size={20} />
                      <span className={cx('font-mono text-[12.5px]', t.status === 'deprecated' ? 'text-ink3 line-through' : 'text-ink')}>{t.id}</span>
                      <RefChain chain={r.chain} size={11} />
                      <span className="ml-auto font-mono text-[11.5px] text-ink3">{t.kind === 'color' ? r.value : (t.kind === 'spacing' || t.kind === 'radius') ? r.value + 'px' : ''}</span>
                    </div>
                  );
                })}
              </div>
            )}
          </CompSection>

          {/* Changelog — auto-compiled */}
          <CompSection icon="diff" title={tr('Changelog', '更新日志')} source="auto" note={tr('Compiled from changesets at each release.', '发版时由 changeset 自动汇编。')}>
            <div className="flex flex-col gap-3.5">
              {original.changelog.map(cl => (
                <div key={cl.v} className="flex gap-3">
                  <div className="shrink-0 w-[84px]">
                    <span className="font-mono text-[12.5px] text-ink">v{cl.v}</span>
                    <div className="text-[11px] text-ink3">{cl.date}</div>
                  </div>
                  <ul className="flex-1 flex flex-col gap-1 border-l border-line pl-3">
                    {(zh && cl.notesZh && cl.notesZh.length ? cl.notesZh : cl.notes).map((n, i) => <li key={i} className="text-[12.5px] text-ink2 leading-snug">{n}</li>)}
                  </ul>
                </div>
              ))}
            </div>
          </CompSection>

          <div className="flex items-center justify-between text-[11.5px] text-ink3 px-1 pb-2">
            <span>{tr('Last edit by', '最近编辑')} <span className="text-ink2">@{original.lastEditBy}</span> · {original.lastEditAt} · <span className="font-mono">{original.lastCommit}</span></span>
            <span className="inline-flex items-center gap-1.5"><Icon name="branch" size={12} /><span className="font-mono">{SESSION.branch}</span></span>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ---- module entry ---- */
function ComponentsPage({ ctx, focusId, clearFocus, onSaveComponent, onViewInGraph }) {
  const [sel, setSel] = useStateCP(focusId || null);
  React.useEffect(() => { if (focusId) setSel(focusId); }, [focusId]);

  if (sel && COMPONENT_MAP[sel]) {
    return <ComponentDetail key={sel} id={sel} ctx={ctx}
      onBack={() => { setSel(null); if (clearFocus) clearFocus(); }}
      onSave={onSaveComponent} onViewInGraph={onViewInGraph} />;
  }
  return <ComponentsList onSelect={setSel} />;
}

Object.assign(window, { ComponentsPage, CompStatusTag });
