/* AssetsPage.jsx — icon / image library gallery + asset drawer */
const { useState: useStateAS, useMemo: useMemoAS } = React;

/* striped placeholder for image assets */
function ImagePlaceholder({ label, dims, ratio='16 / 10', className='', big }) {
  return (
    <div className={cx('relative w-full overflow-hidden rounded-[8px] bg-sunken border border-line', className)} style={{ aspectRatio:ratio }}>
      <svg className="absolute inset-0 w-full h-full" preserveAspectRatio="none">
        <defs>
          <pattern id={'st'+label} width="9" height="9" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
            <line x1="0" y1="0" x2="0" y2="9" stroke="var(--border)" strokeWidth="6" />
          </pattern>
        </defs>
        <rect width="100%" height="100%" fill={'url(#st'+label+')'} opacity="0.6" />
      </svg>
      <div className="absolute inset-0 flex flex-col items-center justify-center gap-1">
        <Icon name="image" size={big?22:16} className="text-ink3" />
        <span className="font-mono text-ink3" style={{ fontSize: big?12:10 }}>{dims}</span>
      </div>
    </div>
  );
}

/* asset card */
function AssetCard({ asset, selected, onClick }) {
  const deprecated = asset.status==='deprecated';
  return (
    <button onClick={onClick}
      className={cx('group relative flex flex-col rounded-[12px] border bg-panel text-left transition-all duration-150 overflow-hidden',
        selected ? 'border-accent ring-1 ring-[color:var(--accent)]' : 'border-line hover:border-line2 hover:bg-hover', deprecated&&'opacity-60')}>
      <div className="absolute top-2 right-2 z-[1]">{asset.status!=='published' && <StatusTag status={asset.status} />}</div>
      {asset.uploaded && asset.url ? (
        <div className="aspect-[4/3] flex items-center justify-center bg-sunken border-b border-line overflow-hidden checker">
          <img src={asset.url} alt={asset.id} className="max-w-[78%] max-h-[78%] object-contain" />
        </div>
      ) : asset.type==='icon' ? (
        <div className="aspect-[4/3] flex items-center justify-center bg-sunken border-b border-line text-ink">
          <div className="w-12 h-12 rounded-[12px] bg-panel border border-line flex items-center justify-center group-hover:scale-105 transition-transform">
            <Icon name={asset.icon} size={24} stroke={1.6} />
          </div>
        </div>
      ) : (
        <div className="p-2.5 bg-sunken border-b border-line">
          <ImagePlaceholder label={asset.id} dims={asset.dims} ratio={asset.id.includes('banner')?'4 / 1':asset.id.includes('avatar')?'1 / 1':'16 / 10'} />
        </div>
      )}
      <div className="px-3 py-2.5">
        <div className="font-mono text-[12px] text-ink truncate">{asset.id}</div>
        <div className="flex items-center gap-1.5 mt-1 text-[11px] text-ink3">
          <span>{asset.format}</span><span>·</span><span>{asset.dims}</span>
          <span className="ml-auto inline-flex items-center gap-1"><Icon name="link" size={11}/>{asset.used}</span>
        </div>
      </div>
    </button>
  );
}

/* drop zone */
function DropZone({ big, onUpload }) {
  const [over, setOver] = useStateAS(false);
  return (
    <div onDragOver={e=>{e.preventDefault();setOver(true);}} onDragLeave={()=>setOver(false)} onDrop={e=>{e.preventDefault();setOver(false);onUpload&&onUpload();}}
      className={cx('flex flex-col items-center justify-center text-center rounded-[12px] border-2 border-dashed transition-colors',
        over ? 'border-accent bg-accentWeak' : 'border-line2 bg-sunken', big?'py-16 px-8':'py-7 px-6')}>
      <div className={cx('rounded-full bg-panel border border-line flex items-center justify-center text-ink2 mb-3', big?'w-14 h-14':'w-10 h-10')}>
        <Icon name="upload" size={big?22:16} />
      </div>
      <h3 className={cx('font-semibold text-ink', big?'text-[15px]':'text-[13px]')}>{tr('Drop SVG or PNG files to upload','拖入 SVG 或 PNG 文件上传')}</h3>
      <p className="text-[12.5px] text-ink2 mt-1">{tr('or ','或 ')}<button onClick={onUpload} className="text-accent hover:underline">{tr('browse files','浏览文件')}</button>{tr('. Each asset gets a stable token id.','。每个资源都会获得一个稳定的 token id。')}</p>
    </div>
  );
}

/* upload card — always the first cell of the gallery, so it stays visible */
function UploadCard({ onUpload }) {
  return (
    <button onClick={onUpload}
      className="rounded-[12px] border-2 border-dashed border-line2 bg-sunken flex flex-col items-center justify-center gap-2 min-h-[148px] text-ink3 hover:text-accent hover:border-accent hover:bg-accentWeak transition">
      <span className="w-10 h-10 rounded-full bg-panel border border-line flex items-center justify-center"><Icon name="upload" size={16} /></span>
      <span className="text-[12.5px] font-medium">{tr('Upload asset','上传资源')}</span>
    </button>
  );
}

/* asset filter groups (size + usage) for the shared <FilterMenu>; built at render for tr() reactivity */
function assetFilterGroups(sizes, usages) {
  return [
    { key:'size',  label:tr('Size','尺寸'),      opts:[['all', tr('All','全部')]].concat(sizes.map(s=>[s,s])) },
    { key:'usage', label:tr('Used in','使用场景'), opts:[['all', tr('All','全部')]].concat(usages.map(u=>[u,u])) },
  ];
}

/* ---- asset drawer ---- */
function AssetDrawer({ asset, ctx, onClose, onDeprecate, onSaveContent }) {
  const [tags, setTags] = useStateAS(asset.tags);
  const [keywords, setKeywords] = useStateAS(asset.keywords);
  const [desc, setDesc] = useStateAS(asset.desc || '');
  const [showUsage, setShowUsage] = useStateAS(false);
  const [confirmDep, setConfirmDep] = useStateAS(false);
  const [saved, setSaved] = useStateAS(false);
  const deprecated = asset.status==='deprecated';
  // editable identity (only for user-created / new assets — changing a built-in id breaks references)
  const editableId = !!(asset.uploaded || asset.status==='new');
  const [idDraft, setIdDraft] = useStateAS(asset.id);
  const [typeDraft, setTypeDraft] = useStateAS(asset.type);
  const [idErr, setIdErr] = useStateAS('');
  // category editing — pick an existing category for this type, or coin a new one
  const catOptions = Array.from(new Set(ASSETS.filter(a=>a.type===typeDraft).map(a=>a.category)));
  const [catSel, setCatSel] = useStateAS(asset.category);
  const [newCat, setNewCat] = useStateAS('');
  const catIsNew = catSel === '__new__';
  // icon standard label — single source of truth lives in the Content module (surface 'icon-label', linked by iconId)
  const lbl = (typeof copyForIcon!=='undefined') ? copyForIcon(asset.id) : null;
  const [labelEn, setLabelEn] = useStateAS(lbl ? lbl.en : '');
  const [labelZh, setLabelZh] = useStateAS(lbl ? lbl.zh : '');
  function persistLabel() {
    if (!lbl) return;
    if ((labelEn.trim() && labelEn !== lbl.en) || (labelZh.trim() && labelZh !== lbl.zh)) {
      const from = lbl.en;
      lbl.en = labelEn.trim() || lbl.en; lbl.zh = labelZh.trim() || lbl.zh;
      if (lbl.status === 'published') lbl.status = 'modified';
      if (onSaveContent) onSaveContent({ action:'modified', target:'copy:'+lbl.key, diffType:'copyEdit', from, to: lbl.en });
    }
  }
  // commit identity / category edits to the in-memory asset on save
  function persistMeta() {
    // type
    if (editableId && typeDraft !== asset.type) asset.type = typeDraft;
    // category (existing pick or freshly coined name)
    const finalCat = catIsNew ? newCat.trim() : catSel;
    if (finalCat && finalCat !== asset.category) asset.category = finalCat;
    // id — only when editable, changed, and unique
    if (editableId) {
      const nextId = idDraft.trim();
      if (nextId && nextId !== asset.id && !ASSETS.some(a=>a.id===nextId)) {
        if (lbl) lbl.iconId = nextId; // keep the linked icon-label in sync
        asset.id = nextId;
      }
    }
  }
  return (
    <aside className="w-[400px] shrink-0 border-l border-line bg-panel flex flex-col h-full" style={{ animation:'fadeIn 120ms ease' }}>
      <div className="flex items-start justify-between px-5 pt-4 pb-3 border-b border-line">
        <div className="min-w-0">
          <div className="text-[11px] text-ink3 uppercase tracking-wide mb-1">{asset.type==='icon'?tr('Icon','图标'):tr('Image','图片')} · {asset.category}</div>
          <h2 className="font-mono text-[15px] text-ink font-medium truncate">{asset.id}</h2>
        </div>
        <IconButton name="close" title="Close" onClick={onClose} />
      </div>
      <div className="flex-1 overflow-y-auto ds-scroll px-5 py-4 flex flex-col gap-5">
        {/* big preview */}
        <div className="rounded-[12px] border border-line bg-sunken p-6 flex items-center justify-center min-h-[150px] overflow-hidden">
          {asset.uploaded && asset.url
            ? <img src={asset.url} alt={asset.id} className="max-w-full max-h-[200px] object-contain" />
            : asset.type==='icon'
            ? <div className="w-20 h-20 rounded-[12px] bg-panel border border-line flex items-center justify-center text-ink"><Icon name={asset.icon} size={40} stroke={1.5} /></div>
            : <ImagePlaceholder big label={asset.id+'-big'} dims={asset.dims} ratio={asset.id.includes('banner')?'4 / 1':asset.id.includes('avatar')?'1 / 1':'16 / 10'} className="max-w-[280px]" />}
        </div>

        <Field label={tr('Token id','Token ID')} error={idErr || undefined} hint={editableId ? undefined : tr('Built-in id is locked — other modules reference it.','内置 id 已锁定 —— 其他模块在引用它。')}>
          {editableId
            ? <TextInput mono value={idDraft} disabled={!canEdit()} onChange={e=>{ setIdDraft(e.target.value); const v=e.target.value.trim(); setIdErr(v && v!==asset.id && ASSETS.some(a=>a.id===v) ? tr('Id already exists','该 id 已存在') : ''); }} />
            : <TextInput mono value={asset.id} readOnly />}
        </Field>
        <div className="grid grid-cols-2 gap-3">
          <Field label={tr('Type','类型')}>
            {editableId
              ? <Segmented size="sm" value={typeDraft} onChange={t=>{ setTypeDraft(t); }} options={[
                  {value:'icon',label:tr('Icon','图标'),icon:'grid'},
                  {value:'image',label:tr('Image','图片'),icon:'image'},
                ]} />
              : <TextInput value={asset.type==='icon'?tr('Icon','图标'):tr('Image','图片')} readOnly />}
          </Field>
          <Field label={tr('Format','格式')}><TextInput mono value={asset.format} readOnly /></Field>
        </div>
        <div className="grid grid-cols-2 gap-3">
          <Field label={tr('Category','分类')}>
            <select value={catSel} disabled={!canEdit()} onChange={e=>{ setCatSel(e.target.value); if(e.target.value!=='__new__') setNewCat(''); }}
              className="w-full h-8 bg-sunken border border-line rounded-[8px] px-2.5 text-[13px] text-ink focus-ring focus:border-line2">
              {catOptions.map(c=><option key={c} value={c}>{c}</option>)}
              <option value="__new__">{tr('+ New category…','+ 新建分类…')}</option>
            </select>
          </Field>
          {catIsNew && (
            <Field label={tr('New category','新分类名称')}>
              <TextInput value={newCat} disabled={!canEdit()} autoFocus placeholder={tr('e.g. Sensor','如 Sensor')} onChange={e=>setNewCat(e.target.value)} />
            </Field>
          )}
        </div>

        {/* standard text label — edits here flow to the Content module & the public icon library */}
        {lbl && (
          <div className="rounded-[12px] border border-line bg-sunken p-3.5 flex flex-col gap-3">
            <div className="flex items-center justify-between">
              <span className="text-[12px] font-medium text-ink2">{tr('Standard label','标准文字')}</span>
              <span className="inline-flex items-center gap-1 text-[10.5px] text-ink3 font-mono">{lbl.key}</span>
            </div>
            <div className="grid grid-cols-2 gap-3">
              <Field label="English"><TextInput value={labelEn} onChange={e=>setLabelEn(e.target.value)} disabled={!canEdit()} placeholder="Notifications" /></Field>
              <Field label="中文"><TextInput value={labelZh} onChange={e=>setLabelZh(e.target.value)} disabled={!canEdit()} placeholder="通知" /></Field>
            </div>
            {(ctx && getAppLang()==='zh' ? lbl.contextZh : lbl.context) && <p className="text-[11.5px] text-ink3 leading-relaxed">{getAppLang()==='zh' ? (lbl.contextZh||lbl.context) : lbl.context}</p>}
            <p className="text-[11px] text-ink3 inline-flex items-center gap-1"><Icon name="edit" size={11} />{tr('Shared with Content → Copy strings (icon-label).','与「内容规范 → 字符串」(icon-label) 同源。')}</p>
          </div>
        )}

        {/* file info */}
        <div className="rounded-[12px] border border-line divide-y divide-[color:var(--border)] text-[12.5px]">
          {[[tr('Dimensions','尺寸'),asset.dims],[tr('File size','文件大小'),fmtBytes(asset.bytes)],[tr('Type','类型'),asset.type==='icon'?tr('Vector (SVG)','矢量 (SVG)'):tr('Raster (PNG)','位图 (PNG)')]].map(([k,v])=>(
            <div key={k} className="flex items-center justify-between px-3.5 h-9"><span className="text-ink2">{k}</span><span className="font-mono text-ink">{v}</span></div>
          ))}
        </div>

        <Field label={tr('Usage scene','使用场景')} hint={tr('Where & when to use this asset — also used for search.','该资源的使用场景与时机 —— 也用于搜索。')}>
          <textarea value={desc} onChange={e=>setDesc(e.target.value)} rows={2} placeholder={tr('e.g. Critical alerts on HMI dashboards','如 HMI 仪表盘上的严重告警')}
            className="w-full bg-sunken border border-line rounded-[8px] px-2.5 py-2 text-[13px] text-ink focus-ring resize-none" />
        </Field>

        <Field label={tr('Keywords','关键词')} hint={tr('Improves search & autosuggest.','提升搜索与自动建议。')}>
          <TagInput value={keywords} onChange={setKeywords} disabled={!canEdit()} placeholder={tr('Add keyword…','添加关键词…')} />
        </Field>

        {/* used by — expandable list of referencing components / screens */}
        <div className="rounded-[12px] border border-line overflow-hidden">
          <div className="flex items-center gap-2 px-3.5 py-3 text-[13px] text-ink">
            <Icon name="link" size={14} className="text-ink2" />{tr('Referenced by','被引用')} <b className="font-semibold">{asset.usedBy.length || asset.used}</b> {(asset.usedBy.length || asset.used)===1?tr('place','处'):tr('places','处')}
            <button onClick={()=>setShowUsage(s=>!s)} className="ml-auto text-[12px] text-accent hover:underline">{showUsage?tr('Hide','收起'):tr('View usage','查看引用')}</button>
          </div>
          {showUsage && (
            <div className="border-t border-line divide-y divide-[color:var(--border)]">
              {asset.usedBy.length>0 ? asset.usedBy.map(u=>(
                <div key={u} className="flex items-center gap-2 px-3.5 h-9 text-[12.5px] text-ink2"><Icon name="component" size={13} className="text-ink3"/>{u}</div>
              )) : <div className="px-3.5 py-3 text-[12px] text-ink3">{tr('No detailed usage recorded yet.','暂无详细引用记录。')}</div>}
            </div>
          )}
        </div>

      </div>

      <div className="border-t border-line px-5 py-3 flex flex-col gap-2.5">
        <div className="flex items-center gap-2">
          <Button variant="secondary" icon="refresh" className="flex-1">{tr('Replace file','替换文件')}</Button>
          <Button variant="secondary" icon="download" className="flex-1">{tr('Download','下载')}</Button>
        </div>
        <div className="flex items-center justify-between gap-2">
          <div>
            {(!deprecated && canEdit())
              ? (confirmDep
                  ? <ConfirmInline open={true} label={tr('Deprecate asset?','废弃该资源?')} onConfirm={()=>{onDeprecate(asset.id);setConfirmDep(false);onClose();}} onCancel={()=>setConfirmDep(false)} />
                  : <Button variant="danger" icon="trash" onClick={()=>setConfirmDep(true)}>{tr('Deprecate','废弃')}</Button>)
              : (deprecated ? <span className="text-[12px] text-ink3 inline-flex items-center gap-1.5"><span className="w-1.5 h-1.5 rounded-full bg-ink3"/>{tr('Deprecated','已废弃')}</span> : null)}
          </div>
          <div className="flex items-center gap-2">
            <Button variant="ghost" onClick={onClose}>{tr('Cancel','取消')}</Button>
            <Button variant="primary" icon={saved?'check':undefined} disabled={!canEdit()} onClick={()=>{asset.desc=desc;asset.keywords=keywords;asset.tags=tags;persistLabel();persistMeta();setSaved(true);setTimeout(()=>{setSaved(false);onClose();},650);}}>{saved?tr('Saved','已保存'):tr('Save draft','保存草稿')}</Button>
          </div>
        </div>
      </div>
    </aside>
  );
}

/* ---- collapsible multi-select type filter (subordinate to the icon/image axis) ---- */
function CategoryMultiSelect({ options, selected, onChange }) {
  const [open, setOpen] = useStateAS(false);
  const toggle = (c) => onChange(selected.includes(c) ? selected.filter(x=>x!==c) : [...selected, c]);
  return (
    <div className="relative">
      <Button variant="secondary" size="sm" iconRight="chevronD" onClick={()=>setOpen(o=>!o)} className={selected.length ? 'border-accent text-accent' : ''}>
        {tr('Type','类型')}{selected.length>0 && <CountBadge n={selected.length} tone="accent" />}
      </Button>
      {open && (<>
        <div className="fixed inset-0 z-10" onClick={()=>setOpen(false)} />
        <div className="absolute right-0 top-9 z-20 bg-panel border border-line rounded-[12px] p-2 shadow-[0_8px_28px_rgba(0,0,0,0.14)] flex flex-col gap-0.5 min-w-[180px]" style={{ animation:'fadeIn 120ms ease' }}>
          <button onClick={()=>onChange([])}
            className={cx('flex items-center gap-2 h-8 px-2 rounded-[8px] text-[13px] transition', selected.length===0 ? 'text-accent' : 'text-ink2 hover:bg-hover hover:text-ink')}>
            <span className="w-4 h-4 inline-flex items-center justify-center">{selected.length===0 && <Icon name="check" size={13} />}</span>
            {tr('All','全部')}
          </button>
          {options.length>0 && <div className="h-px bg-line my-1" />}
          {options.map(c=>{
            const on = selected.includes(c);
            return (
              <button key={c} onClick={()=>toggle(c)}
                className={cx('flex items-center gap-2 h-8 px-2 rounded-[8px] text-[13px] transition', on ? 'text-ink' : 'text-ink2 hover:bg-hover hover:text-ink')}>
                <span className={cx('w-4 h-4 rounded-[5px] border inline-flex items-center justify-center shrink-0', on ? 'bg-accent border-accent text-onAccent' : 'border-line2')}>
                  {on && <Icon name="check" size={11} />}
                </span>
                {c}
              </button>
            );
          })}
        </div>
      </>)}
    </div>
  );
}

/* ---- page ---- */
function AssetsPage({ ctx, rev, focusId, clearFocus, onSaveContent }) {
  const [type, setType] = useStateAS('icon');
  const [q, setQ] = useStateAS('');
  const [selectedCats, setSelectedCats] = useStateAS([]); // [] = all categories
  const [filters, setFilters] = useStateAS({ size:'all', usage:'all' });
  const [sel, setSel] = useStateAS(null);
  const [rev2, setRev2] = useStateAS(0);
  const fileRef = React.useRef(null);

  React.useEffect(() => { if (focusId) { const a = ASSETS.find(x => x.id === focusId); if (a) { setType(a.type); setSel(a); } if (clearFocus) clearFocus(); } }, [focusId]);

  /* real upload — SVG → icon, raster → image; stored as a session object URL so it works in the live app & front-end */
  function doUpload(fileList) {
    const files = fileList && fileList.length ? [...fileList] : null;
    if (!files) { if (fileRef.current) fileRef.current.click(); return; }
    let last = null;
    files.forEach(f => {
      const isImg = /^image\//.test(f.type) || /\.(png|jpe?g|gif|webp|avif|svg)$/i.test(f.name);
      if (!isImg) return;
      const isSvg = /svg/i.test(f.type) || /\.svg$/i.test(f.name);
      const url = URL.createObjectURL(f);
      const t = isSvg ? 'icon' : 'image';
      const slug = (f.name.replace(/\.[^.]+$/,'').toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/(^-|-$)/g,'')) || 'asset';
      let id = (t==='icon'?'icon.custom.':'image.custom.') + slug, n = 1;
      while (ASSETS.some(a=>a.id===id)) id = (t==='icon'?'icon.custom.':'image.custom.') + slug + '-' + (++n);
      const asset = { id, type:t, category:'Custom', keywords:[slug], icon:null,
        format:(f.name.split('.').pop()||'').toUpperCase(), dims: isSvg?'SVG':'—', bytes:f.size,
        used:0, usedBy:[], status:'new', desc:'', tags:['custom', t], uploaded:true, url };
      ASSETS.push(asset); last = asset;
      // every newly-added asset gets an editable Standard-label entry so the drawer's label section appears
      if (typeof COPY!=='undefined' && typeof COPY_MAP!=='undefined') {
        const prettyName = (f.name.replace(/\.[^.]+$/,'') || slug).replace(/[^a-zA-Z0-9]+/g,' ').trim()
          .replace(/\b\w/g, c=>c.toUpperCase()) || 'Asset';
        let key = 'iconlabel.'+slug, kn = 1;
        while (COPY_MAP[key]) key = 'iconlabel.'+slug+'-'+(++kn);
        const entry = { key, surface:'icon-label', en:prettyName, zh:prettyName, context:'', contextZh:'',
          componentId:null, iconId:asset.id, themes:['all'], good:'', goodZh:'', bad:'', badZh:'',
          vars:[], tags:['icon-label'], status:'new' };
        COPY.push(entry); COPY_MAP[key] = entry;
      }
      if (!isSvg) { const im = new Image(); im.onload = () => { asset.dims = im.naturalWidth + '×' + im.naturalHeight; setRev2(v=>v+1); }; im.src = url; }
    });
    if (last) { setType(last.type); setSelectedCats([]); setSel(last); }
    setRev2(v=>v+1);
  }

  const cats = useMemoAS(() => Array.from(new Set(ASSETS.filter(a=>a.type===type).map(a=>a.category))), [type, rev2]);
  const sizes = useMemoAS(() => Array.from(new Set(ASSETS.filter(a=>a.type===type).map(a=>a.dims))), [type, rev2]);
  const usages = useMemoAS(() => Array.from(new Set(ASSETS.filter(a=>a.type===type).flatMap(a=>a.usedBy||[]))).sort(), [type, rev2]);
  const items = useMemoAS(() => ASSETS.filter(a => a.type===type
    && (selectedCats.length===0||selectedCats.includes(a.category))
    && (filters.size==='all'||a.dims===filters.size)
    && (filters.usage==='all'||(a.usedBy||[]).includes(filters.usage))
    && (!q || a.id.toLowerCase().includes(q.toLowerCase()) || a.category.toLowerCase().includes(q.toLowerCase()) || (a.desc||'').toLowerCase().includes(q.toLowerCase()) || a.keywords.some(k=>k.toLowerCase().includes(q.toLowerCase())) || (a.usedBy||[]).some(u=>u.toLowerCase().includes(q.toLowerCase())) || (a.tags&&a.tags.some(t=>t.toLowerCase().includes(q.toLowerCase()))))), [type,selectedCats,q,filters,rev,rev2]);

  const iconCount = ASSETS.filter(a=>a.type==='icon').length;
  const imageCount = ASSETS.filter(a=>a.type==='image').length;
  const filtersActive = filters.size!=='all' || filters.usage!=='all';
  const empty = items.length===0 && !q && selectedCats.length===0 && !filtersActive;

  return (
    <div className="h-full flex flex-col">
      <PageToolbar
        title={tr('Asset library','资源库')}
        count={items.length}
        leadingActions={
          <Segmented size="sm" value={type} onChange={t=>{setType(t);setSelectedCats([]);setFilters({size:'all',usage:'all'});}} options={[
            {value:'icon',label:`${tr('Icons','图标')} · ${iconCount}`,icon:'grid'},
            {value:'image',label:`${tr('Images','图片')} · ${imageCount}`,icon:'image'},
          ]} />
        }
        search={{ value:q, onChange:setQ, placeholder:tr('Search id, keyword, usage…','搜索 id、关键词、场景…') }}
        filters={{ groups:assetFilterGroups(sizes, usages), value:filters, onChange:setFilters }}
        facets={<CategoryMultiSelect options={cats} selected={selectedCats} onChange={setSelectedCats} />}
      />

      <input ref={fileRef} type="file" accept=".svg,.png,.jpg,.jpeg,.gif,.webp,.avif,image/*" multiple className="hidden" onChange={e=>{ doUpload(e.target.files); e.target.value=''; }} />
      <div className="flex-1 min-h-0 flex">
        <div className="flex-1 min-w-0 overflow-y-auto ds-scroll p-6 [scrollbar-gutter:stable]">
          {empty ? (
            <DropZone big onUpload={doUpload} />
          ) : (
            <>
              {(q||selectedCats.length>0||filtersActive) && items.length===0
                ? <EmptyState icon="search" title={tr('No assets match','没有匹配的资源')} body={tr('Try a different search or filter.','换个搜索词或筛选试试。')} action={<Button variant="secondary" onClick={()=>{setQ('');setSelectedCats([]);setFilters({size:'all',usage:'all'});}}>{tr('Reset','重置')}</Button>} />
                : <div className="grid gap-3" style={{ gridTemplateColumns:'repeat(auto-fill, minmax(176px, 1fr))' }}>
                    {canEdit() && <UploadCard onUpload={doUpload} />}
                    {items.map(a => <AssetCard key={a.id} asset={a} selected={sel?.id===a.id} onClick={()=>setSel(a)} />)}
                  </div>}
            </>
          )}
        </div>
        {sel && <AssetDrawer key={sel.id} asset={sel} ctx={ctx} onSaveContent={onSaveContent} onClose={()=>setSel(null)} onDeprecate={(id)=>{ const a=ASSETS.find(x=>x.id===id); if(a) a.status='deprecated'; setRev2(v=>v+1); }} />}
      </div>
    </div>
  );
}

Object.assign(window, { AssetsPage });
