// filter-screens.jsx — Catalog browse + three config presentations.
//   CatalogScreen (mode: 'sheet' | 'inline')  · FullPageConfig  · ConfigSheet
// All share ConfigBody (filter meta + FilterForm). Designed for a 402-wide frame.

const { useState: uss, useMemo: usm } = React;

// ── small bits ───────────────────────────────────────────────────
function CatDot({ cat, size = 9 }) {
  return <span style={{ width: size, height: size, borderRadius: size / 2, background: cat.dot, flexShrink: 0, display: 'inline-block' }}/>;
}
function Badge({ children, cat }) {
  const t = window.flutterTokens;
  return (
    <span style={{
      fontSize: 10.5, fontWeight: 700, letterSpacing: 0.2,
      color: cat ? cat.ink : t.ink3, background: cat ? cat.tint : t.surfaceAlt,
      padding: '2px 7px', borderRadius: 5, whiteSpace: 'nowrap',
    }}>{children}</span>
  );
}

// header bar — matches app chrome (status-bar gap + back + title + CTA)
function HeaderBar({ title, subtitle, onBack, cta, onCta, ctaDisabled }) {
  const t = window.flutterTokens;
  return (
    <div style={{ padding: '56px 12px 12px', background: t.surface, borderBottom: `1px solid ${t.hairline}`, display: 'flex', alignItems: 'center', gap: 6, flexShrink: 0 }}>
      {onBack && (
        <button onClick={onBack} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 6, color: t.ink, display: 'flex' }}>
          <window.Icon name="back" size={22}/>
        </button>
      )}
      <div style={{ flex: 1, minWidth: 0, paddingLeft: onBack ? 0 : 6 }}>
        <div style={{ fontSize: 16, fontWeight: 700, color: t.ink, letterSpacing: -0.2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{title}</div>
        {subtitle && <div style={{ fontSize: 11.5, color: t.ink3, marginTop: 1 }}>{subtitle}</div>}
      </div>
      {cta && (
        <button onClick={onCta} disabled={ctaDisabled} style={{
          background: ctaDisabled ? t.surfaceAlt : t.ink, color: ctaDisabled ? t.ink3 : t.bg, border: 'none',
          padding: '9px 17px', borderRadius: 999, fontSize: 13.5, fontWeight: 700, cursor: ctaDisabled ? 'default' : 'pointer',
          fontFamily: 'inherit', opacity: ctaDisabled ? 0.7 : 1, flexShrink: 0,
        }}>{cta}</button>
      )}
    </div>
  );
}

// ── filter meta strip (category · badges · desc) ─────────────────
function FilterMeta({ filter }) {
  const t = window.flutterTokens;
  const cat = window.CAT_BY_KEY[filter.category];
  return (
    <div style={{ marginBottom: 18 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, fontWeight: 700, color: cat.ink }}>
          <CatDot cat={cat}/>{cat.label}
        </span>
        {filter.repeatable && <Badge>可重複加入</Badge>}
        {filter.verifiable && <Badge cat={cat}>可驗證</Badge>}
      </div>
      {filter.desc && <div style={{ fontSize: 12.5, color: t.ink2, marginTop: 8, lineHeight: 1.5 }}>{filter.desc}</div>}
    </div>
  );
}

// ── ConfigBody — meta + the SDUI-driven form ─────────────────────
function ConfigBody({ filter, value, setValue, showMeta = true }) {
  return (
    <div>
      {showMeta && <FilterMeta filter={filter}/>}
      <window.FilterForm filter={filter} value={value} setValue={setValue}/>
    </div>
  );
}

function useFilterValue(filter, initial) {
  return uss(() => ({ ...window.defaultsFor(filter), ...(initial || {}) }));
}

// ═══ Presentation A — bottom sheet over the catalog ══════════════
function ConfigSheet({ filter, initial, onClose, onAdd }) {
  const t = window.flutterTokens;
  const [value, setValue] = useFilterValue(filter, initial);
  return (
    <div onClick={onClose} style={{ position: 'absolute', inset: 0, background: 'rgba(31,27,22,0.36)', display: 'flex', alignItems: 'flex-end', zIndex: 70 }}>
      <div onClick={e => e.stopPropagation()} style={{
        width: '100%', background: t.bg, borderRadius: '22px 22px 0 0', maxHeight: '88%',
        display: 'flex', flexDirection: 'column', animation: 'slideUp 240ms cubic-bezier(.2,.7,.2,1)',
      }}>
        <div style={{ width: 36, height: 4, background: t.hairline, borderRadius: 2, margin: '8px auto 4px', flexShrink: 0 }}/>
        <div style={{ padding: '6px 18px 12px', display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
          <div style={{ flex: 1, fontSize: 18, fontWeight: 700, color: t.ink, letterSpacing: -0.3 }}>{filter.label}</div>
          <button onClick={onClose} style={{ background: t.surfaceAlt, border: 'none', borderRadius: 999, width: 30, height: 30, cursor: 'pointer', color: t.ink2, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            <window.Icon name="x" size={15}/>
          </button>
        </div>
        <div style={{ flex: 1, overflowY: 'auto', padding: '4px 18px 18px' }}>
          <ConfigBody filter={filter} value={value} setValue={setValue}/>
        </div>
        <div style={{ padding: '12px 18px 22px', borderTop: `1px solid ${t.hairline}`, background: t.surface, flexShrink: 0 }}>
          <button onClick={() => onAdd(value)} style={ctaFull(t)}>加入條件</button>
        </div>
      </div>
    </div>
  );
}

// ═══ Presentation D — centered dialog over the catalog ══════════
function CenterDialogConfig({ filter, initial, onClose, onAdd, addLabel = '新增' }) {
  const t = window.flutterTokens;
  const [value, setValue] = useFilterValue(filter, initial);
  const cat = window.CAT_BY_KEY[filter.category];
  return (
    <div onClick={onClose} style={{ position: 'absolute', inset: 0, background: 'rgba(31,27,22,0.40)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 70, padding: 22, overflowY: 'auto' }}>
      <div onClick={e => e.stopPropagation()} style={{
        width: '100%', maxWidth: 354, background: t.surface, borderRadius: 20,
        display: 'flex', flexDirection: 'column', overflow: 'visible',
        boxShadow: '0 30px 70px -20px rgba(31,27,22,0.5)',
      }}>
        {/* title row */}
        <div style={{ padding: '16px 18px 12px', display: 'flex', alignItems: 'center', gap: 8, borderBottom: `1px solid ${t.hairline}`, flexShrink: 0 }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 16.5, fontWeight: 700, color: t.ink, letterSpacing: -0.3 }}>{filter.label}</div>
            <div style={{ fontSize: 11, color: t.ink3, marginTop: 1 }}>{cat.label}・設定條件</div>
          </div>
        </div>
        {/* body (overflow visible so dropdown overlays aren't clipped) */}
        <div style={{ padding: '16px 18px 18px' }}>
          <ConfigBody filter={filter} value={value} setValue={setValue} showMeta={false}/>
        </div>
        {/* footer actions */}
        <div style={{ display: 'flex', gap: 8, padding: '12px 16px 16px', borderTop: `1px solid ${t.hairline}`, flexShrink: 0 }}>
          <button onClick={onClose} style={{ flex: 1, background: t.surfaceAlt, color: t.ink, border: 'none', borderRadius: 12, padding: '13px', fontSize: 14, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}>取消</button>
          <button onClick={() => onAdd(value)} style={{ flex: 1, background: t.ink, color: t.bg, border: 'none', borderRadius: 12, padding: '13px', fontSize: 14, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit' }}>{addLabel}</button>
        </div>
      </div>
    </div>
  );
}

// ═══ Presentation B — full page ══════════════════════════════════
function FullPageConfig({ filter, initial, onBack, onAdd }) {
  const t = window.flutterTokens;
  const [value, setValue] = useFilterValue(filter, initial);
  return (
    <div style={{ height: '100%', background: t.bg, display: 'flex', flexDirection: 'column', position: 'relative', overflow: 'hidden' }}>
      <HeaderBar title={filter.label} subtitle="設定條件" onBack={onBack || (() => {})} cta="加入" onCta={onAdd}/>
      <div style={{ flex: 1, overflowY: 'auto', padding: '18px 18px 32px' }}>
        <ConfigBody filter={filter} value={value} setValue={setValue}/>
      </div>
    </div>
  );
}

// ═══ Presentation C — inline accordion inside the catalog ════════
function InlineConfig({ filter, onAdd }) {
  const t = window.flutterTokens;
  const [value, setValue] = useFilterValue(filter, null);
  return (
    <div style={{ padding: '4px 16px 16px', background: t.bg, borderBottom: `1px solid ${t.hairline}` }}>
      <div style={{ background: t.surface, border: `1px solid ${t.hairline}`, borderRadius: 14, padding: 16 }}>
        <ConfigBody filter={filter} value={value} setValue={setValue} showMeta={false}/>
        <button onClick={onAdd} style={{ ...ctaFull(t), marginTop: 18 }}>加入條件</button>
      </div>
    </div>
  );
}

function ctaFull(t) {
  return { width: '100%', background: t.ink, color: t.bg, border: 'none', borderRadius: 13, padding: '14px', fontSize: 14.5, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit' };
}

// ── catalog row ──────────────────────────────────────────────────
function StarIcon({ filled, color, size = 19 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill={filled ? color : 'none'} stroke={color} strokeWidth={1.7} strokeLinecap="round" strokeLinejoin="round">
      <path d="M12 3.2l2.6 5.27 5.82.85-4.21 4.1.99 5.79L12 16.98l-5.2 2.73.99-5.79-4.21-4.1 5.82-.85z"/>
    </svg>
  );
}

function FilterRow({ filter, cat, onTap, open, expandable, query, fav, onToggleFav }) {
  const t = window.flutterTokens;
  // highlight the matched query substring inside a label
  const HL = ({ text }) => {
    if (!text) return null;
    if (!query) return text;
    const i = text.toLowerCase().indexOf(query.toLowerCase());
    if (i < 0) return text;
    return (<>{text.slice(0, i)}<mark style={{ background: 'oklch(0.90 0.12 90)', color: t.ink, borderRadius: 3, padding: '0 1px', fontWeight: 700 }}>{text.slice(i, i + query.length)}</mark>{text.slice(i + query.length)}</>);
  };
  return (
    <button onClick={onTap} style={{
      width: '100%', textAlign: 'left', background: open ? t.surfaceAlt : 'transparent', border: 'none', cursor: 'pointer',
      fontFamily: 'inherit', padding: '13px 16px', display: 'flex', alignItems: 'center', gap: 11,
      borderBottom: `1px solid ${t.hairline}`, transition: 'background 120ms ease',
    }}>
      <span role="button" onClick={(e) => { e.stopPropagation(); onToggleFav && onToggleFav(); }}
        style={{ flexShrink: 0, display: 'flex', cursor: 'pointer', padding: 2, marginLeft: -2 }}>
        <StarIcon filled={!!fav} color={fav ? t.butterDk : t.ink3}/>
      </span>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
          <span style={{ fontSize: 15, fontWeight: 700, color: t.ink, letterSpacing: -0.2 }}><HL text={filter.label}/></span>
        </div>
        {filter.desc && <div style={{ fontSize: 12, color: t.ink3, marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}><HL text={filter.desc}/></div>}
      </div>
    </button>
  );
}

// ── CatalogScreen ────────────────────────────────────────────────
function CatalogScreen({ mode = 'sheet', initialOpenId = null, initialValues = null, initialQuery = '', accordion = false, embedded = false, onBack, onAddFilter }) {
  const t = window.flutterTokens;
  const [q, setQ] = uss(initialQuery);
  const [openId, setOpenId] = uss(initialOpenId);   // sheet: id of filter in sheet · inline: expanded id
  const [openCats, setOpenCats] = uss(() => new Set());   // all categories start collapsed
  const toggleCat = (k) => setOpenCats(prev => { const n = new Set(prev); n.has(k) ? n.delete(k) : n.add(k); return n; });
  const [favs, setFavs] = uss(() => new Set(['foreign_flow', 'ma_cross']));
  const toggleFav = (id) => setFavs(prev => { const n = new Set(prev); n.has(id) ? n.delete(id) : n.add(id); return n; });
  const inline = mode === 'inline';
  const dialog = mode === 'dialog';

  const query = q.trim().toLowerCase();
  const matchF = (f) => !query || f.label.toLowerCase().includes(query) || (f.desc || '').toLowerCase().includes(query);
  const baseGroups = window.CATEGORIES.map(cat => ({ cat, list: (window.FILTERS_BY_CAT[cat.key] || []).filter(matchF) })).filter(g => g.list.length);
  const favList = window.FILTERS.filter(f => favs.has(f.id) && matchF(f));
  const FAV_CAT = { key: '__fav', label: '我的收藏', dot: t.butterDk, tint: 'oklch(0.94 0.06 85)', ink: t.butterDk };
  const groups = favList.length ? [{ cat: FAV_CAT, list: favList }, ...baseGroups] : baseGroups;

  const openFilter = openId ? window.FILTER_BY_ID[openId] : null;
  const tap = (id) => {
    if (inline) setOpenId(prev => prev === id ? null : id);
    else setOpenId(id);
  };

  return (
    <div style={{ height: '100%', background: t.bg, display: 'flex', flexDirection: 'column', position: 'relative' }}>
      <div style={{ background: t.bg, borderBottom: `1px solid ${t.hairline}`, flexShrink: 0 }}>
        <div style={{ padding: (embedded ? '14px' : '56px') + ' 12px 0', display: 'flex', alignItems: 'center', gap: 8 }}>
          <button onClick={onBack} style={{ width: 32, flexShrink: 0, background: 'none', border: 'none', cursor: 'pointer', padding: 6, color: t.ink, display: 'flex', marginLeft: -2 }}>
            <window.Icon name="back" size={22}/>
          </button>
          <div style={{ flex: 1, minWidth: 0, textAlign: 'center' }}>
            <div style={{ fontFamily: '"Plus Jakarta Sans","Noto Sans TC",sans-serif', fontSize: 17, fontWeight: 700, color: t.ink, letterSpacing: -0.3 }}>選擇篩選條件</div>
          </div>
          <div style={{ width: 32, flexShrink: 0 }}/>
        </div>
        {/* search */}
        <div style={{ padding: '12px 16px 14px' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, background: '#F1F3F4', borderRadius: 12, padding: '13px 14px' }}>
            <window.Icon name="search" size={18} color="#5F6368"/>
            <input value={q} onChange={e => setQ(e.target.value)} placeholder="搜尋" style={{ flex: 1, border: 'none', outline: 'none', background: 'transparent', fontFamily: 'inherit', fontSize: 15, color: '#3C4043' }}/>
            {q && <button onClick={() => setQ('')} style={{ background: '#C4C7C9', border: 'none', cursor: 'pointer', padding: 0, width: 20, height: 20, borderRadius: '50%', color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}><window.Icon name="x" size={12}/></button>}
          </div>
        </div>
      </div>

      <div style={{ flex: 1, overflowY: 'auto' }}>
        {groups.length === 0 && (
          <div style={{ padding: '60px 28px', textAlign: 'center', color: t.ink3 }}>
            <div style={{ fontSize: 13.5, fontWeight: 600, color: t.ink2 }}>找不到符合「{q}」的條件</div>
          </div>
        )}
        {groups.map(({ cat, list }) => {
          const catOpen = query ? true : (!accordion || openCats.has(cat.key));
          return (
          <div key={cat.key} style={accordion ? { borderBottom: `1px solid ${t.hairline}` } : undefined}>
            {accordion ? (
              <button onClick={() => !query && toggleCat(cat.key)} disabled={!!query} style={{
                width: '100%', textAlign: 'left', background: 'transparent', border: 'none',
                cursor: query ? 'default' : 'pointer', fontFamily: 'inherit',
                padding: '15px 16px', display: 'flex', alignItems: 'center', gap: 10,
              }}>
                <span style={{ flex: 1, fontSize: 15, fontWeight: 700, color: t.ink, letterSpacing: -0.2 }}>{cat.label}{cat.key === '__fav' && <span style={{ color: t.ink3, fontWeight: 600 }}> ({list.length})</span>}</span>
                <span style={{ display: 'inline-flex', transform: catOpen ? 'rotate(90deg)' : 'none', transition: 'transform 200ms ease', color: t.ink3 }}>
                  <window.Icon name="chevron" size={16} color={t.ink3}/>
                </span>
              </button>
            ) : (
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '16px 16px 7px' }}>
                <span style={{ fontSize: 12, fontWeight: 700, color: t.ink2, letterSpacing: 0.3 }}>{cat.label}</span>
                {cat.key === '__fav' && <span style={{ fontFamily: '"Plus Jakarta Sans",monospace', fontSize: 11, fontWeight: 600, color: t.ink3 }}>{list.length}</span>}
                <div style={{ flex: 1, height: 1, background: t.hairline, marginLeft: 4 }}/>
              </div>
            )}
            {catOpen && list.map(f => (
              <React.Fragment key={f.id}>
                <FilterRow filter={f} cat={window.CAT_BY_KEY[f.category]} query={query} onTap={() => tap(f.id)} open={openId === f.id} expandable={inline}
                  fav={favs.has(f.id)} onToggleFav={() => toggleFav(f.id)}/>
                {inline && openId === f.id && <InlineConfig filter={f} onAdd={() => setOpenId(null)}/>}
              </React.Fragment>
            ))}
          </div>
          );
        })}
        <div style={{ height: 24 }}/>
      </div>

      {/* sheet / dialog presentation */}
      {!inline && openFilter && (
        dialog
          ? <CenterDialogConfig filter={openFilter} initial={openId === initialOpenId ? initialValues : null}
              onClose={() => setOpenId(null)} onAdd={(val) => { onAddFilter && onAddFilter(openFilter.id, val); setOpenId(null); }}/>
          : <ConfigSheet filter={openFilter} initial={openId === initialOpenId ? initialValues : null}
              onClose={() => setOpenId(null)} onAdd={(val) => { onAddFilter && onAddFilter(openFilter.id, val); setOpenId(null); }}/>
      )}
    </div>
  );
}

// ── summarize an applied filter's value into a short line ────────
function summarizeFilter(filter, value) {
  const t = window.flutterTokens;
  const parts = [];
  const hasRange = filter.fields.some(f => f.key === 'min') || filter.fields.some(f => f.key === 'max');
  if (hasRange) {
    const u = (filter.fields.find(f => f.key === 'min' || f.key === 'max') || {}).unit || '';
    const mn = value.min, mx = value.max;
    if (mn != null || mx != null) parts.push(`${mn != null ? mn : '不限'} ~ ${mx != null ? mx : '不限'}${u}`);
  }
  for (const f of filter.fields) {
    const v = value[f.key];
    if (f.kind === 'number' && f.key !== 'min' && f.key !== 'max') {
      if (v != null) parts.push(`${f.label} ${v}${f.unit || ''}`);
    } else if (f.kind === 'choice') {
      const lab = (x) => { const o = f.options.kind === 'static' ? f.options.values.find(y => y.value === x) : null; return o ? o.label : x; };
      if (f.single) { if (v != null) parts.push(lab(v)); }
      else { const arr = v || []; if (arr.length) parts.push(arr.slice(0, 2).map(lab).join('、') + (arr.length > 2 ? ` +${arr.length - 2}` : '')); }
    } else if (f.kind === 'metric') {
      if (v) { const m = window.RANKABLE_METRICS.find(x => x.kind === v.kind); if (m) parts.push(m.label); }
    } else if (f.kind === 'date') { if (v) parts.push(v); }
  }
  return parts.join(' · ') || '未設定';
}

let _afid = 0;
const newAppliedId = () => 'af_' + (++_afid);

// seed a watchlist's current filters (SDUI instances)
function seedApplied() {
  const mk = (id, override) => ({ key: newAppliedId(), filterId: id, value: { ...window.defaultsFor(window.FILTER_BY_ID[id]), ...override } });
  return [
    mk('price', { min: 20, max: 80 }),
    mk('sector', { op: 'include', values: ['半導體', '電子零組件'] }),
    mk('foreign_flow', { days: 5, direction: 'buy', min: 3000, max: null }),
    mk('volume', { min: 5000, max: null }),
  ];
}

// ── swipe-to-delete row (mainstream iOS/Android pattern) ─────────
function SwipeDeleteRow({ open, onOpenChange, onRemove, onTap, children }) {
  const t = window.flutterTokens;
  const ACT = 84;
  const drag = React.useRef(null);
  const moved = React.useRef(false);
  const [dx, setDx] = uss(0);
  React.useEffect(() => { if (!drag.current) setDx(open ? -ACT : 0); }, [open]);
  const down = (e) => { e.currentTarget.setPointerCapture(e.pointerId); moved.current = false; drag.current = { x: e.clientX, base: open ? -ACT : 0 }; };
  const move = (e) => {
    if (!drag.current) return;
    const delta = e.clientX - drag.current.x;
    if (Math.abs(delta) > 4) moved.current = true;
    setDx(Math.max(-ACT - 16, Math.min(0, drag.current.base + delta)));
  };
  const up = () => { if (!drag.current) return; const shouldOpen = dx < -ACT / 2; drag.current = null; setDx(shouldOpen ? -ACT : 0); onOpenChange(shouldOpen); };
  const click = () => {
    if (drag.current) return;
    if (moved.current) { moved.current = false; return; }
    if (open) onOpenChange(false); else onTap && onTap();
  };
  return (
    <div style={{ position: 'relative', overflow: 'hidden', borderBottom: `1px solid ${t.hairline}` }}>
      <button onClick={onRemove} style={{
        position: 'absolute', top: 0, right: 0, bottom: 0, width: ACT, background: t.coralDk, color: '#fff',
        border: 'none', cursor: 'pointer', fontFamily: 'inherit', fontSize: 14, fontWeight: 700, letterSpacing: 1,
        display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4,
      }}>刪除</button>
      <div onPointerDown={down} onPointerMove={move} onPointerUp={up} onPointerCancel={up}
        onClick={click}
        style={{ position: 'relative', background: t.bg, transform: `translateX(${dx}px)`, cursor: 'pointer',
          transition: drag.current ? 'none' : 'transform 220ms cubic-bezier(.2,.7,.2,1)', touchAction: 'pan-y' }}>
        {children}
      </div>
    </div>
  );
}

// ── EditFilterList — current filters, edit mode (removable) ──────
// merged-page table columns + a decreasing remaining-count sequence
const MGCOL = '84px minmax(0, 1fr) 52px 22px';
const MGGAP = 10;
const MLINK = 'oklch(0.50 0.12 245)';
function menuItemStyle(t, danger) {
  return {
    width: '100%', display: 'flex', alignItems: 'center', gap: 12,
    padding: '12px 15px', background: 'transparent', border: 'none', borderRadius: 0,
    cursor: 'pointer', fontFamily: 'inherit', fontSize: 17, fontWeight: 500,
    color: danger ? t.coralDk : t.ink, textAlign: 'left',
  };
}
function mRemaining(originSize, finalSize, n) {
  if (n <= 0) return [];
  const out = []; const ratio = Math.pow(Math.max(finalSize, 1) / originSize, 1 / n);
  let cur = originSize;
  for (let i = 0; i < n; i++) { cur = i === n - 1 ? Math.max(finalSize, 1) : Math.round(cur * ratio); out.push(cur); }
  return out;
}

// inline edit panel — expands beneath a condition row (accordion), applies live
function InlineEditPanel({ filter, value, onChange }) {
  const t = window.flutterTokens;
  const setValue = (updater) => onChange(typeof updater === 'function' ? updater(value) : updater);
  return (
    <div style={{ padding: '2px 16px 14px', background: t.bg, animation: 'expandIn 200ms cubic-bezier(.2,.7,.2,1)' }}>
      <div style={{ background: t.surface, border: `1px solid ${t.hairline}`, borderRadius: 14, padding: 16 }}>
        <window.FilterForm filter={filter} value={value} setValue={setValue}/>
      </div>
    </div>
  );
}

function EditFilterList({ applied, onRemove, onUpdate, onAdd, onSave, onBack, onDelete, onShare, onRename, title = '篩選條件', resultBase = 50, initialEditKey = null, originLabel = '全市場', originSize = 2000, editMode = 'expand', embedded = false }) {
  const t = window.flutterTokens;
  const [openKey, setOpenKey] = uss(null);
  const [menuOpen, setMenuOpen] = uss(false);
  const [editKey, setEditKey] = uss(initialEditKey);
  const expand = editMode === 'expand';
  // changes apply live; result count reacts to the number of active conditions
  const resultN = Math.max(1, Math.round(resultBase * Math.pow(0.86, applied.length - 4)));
  const remaining = mRemaining(originSize, resultN, applied.length);
  const editItem = editKey ? applied.find(x => x.key === editKey) : null;
  const editFilter = editItem ? window.FILTER_BY_ID[editItem.filterId] : null;
  const mFmt = (x) => x.toLocaleString('en-US');
  return (
    <div style={{ height: '100%', background: t.bg, display: 'flex', flexDirection: 'column', position: embedded ? undefined : 'relative' }}>
      <style>{`
        .cond-remove{ background:transparent; color:${t.ink3}; transition:background 140ms ease,color 140ms ease,transform 120ms ease; }
        .cond-remove:hover{ background:rgba(237,110,90,0.14); color:${t.coralDk}; }
        .cond-remove:active{ transform:scale(0.88); }
      `}</style>
      {!embedded && (
      <div style={{ background: t.bg, borderBottom: `1px solid ${t.hairline}`, flexShrink: 0 }}>
        <div style={{ padding: '56px 12px 12px', display: 'flex', alignItems: 'center', gap: 8 }}>
          <button onClick={onBack} style={{ width: 32, flexShrink: 0, background: 'none', border: 'none', cursor: 'pointer', padding: 6, color: t.ink, display: 'flex', marginLeft: -2 }}>
            <window.Icon name="back" size={22}/>
          </button>
          <div style={{ flex: 1, minWidth: 0, textAlign: 'center', fontFamily: '"Plus Jakarta Sans","Noto Sans TC",sans-serif', fontSize: 17, fontWeight: 700, color: t.ink, letterSpacing: -0.3, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{title}</div>
          <div style={{ width: 32, flexShrink: 0, position: 'relative', display: 'flex', justifyContent: 'flex-end' }}>
            {(onDelete || onShare || onRename) && (
              <button onClick={() => setMenuOpen(v => !v)} title="更多" style={{
                width: 32, height: 32, borderRadius: 16, background: menuOpen ? t.surfaceAlt : 'none',
                border: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', color: t.ink,
              }}>
                <window.Icon name="more" size={22}/>
              </button>
            )}
            {menuOpen && (
              <>
                <div onClick={() => setMenuOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 50 }}/>
                <div className="fs-applemenu" style={{
                  position: 'absolute', top: 'calc(100% + 6px)', right: 0, zIndex: 51,
                  minWidth: 176, background: 'rgba(250,249,247,0.86)', backdropFilter: 'blur(20px)', WebkitBackdropFilter: 'blur(20px)', borderRadius: 14,
                  boxShadow: '0 12px 40px -8px rgba(31,27,22,0.32)', padding: 0, overflow: 'hidden',
                  animation: 'expandIn 150ms cubic-bezier(.2,.7,.2,1)',
                }}>
                  {onRename && (
                    <button onClick={() => { setMenuOpen(false); onRename(); }} style={menuItemStyle(t)}>
                      <window.Icon name="edit" size={20} color={t.ink}/>修改名稱
                    </button>
                  )}
                  {onShare && (
                    <button onClick={() => { setMenuOpen(false); onShare(); }} style={menuItemStyle(t)}>
                      <window.Icon name="share" size={20} color={t.ink}/>分享
                    </button>
                  )}
                  {onDelete && (
                    <button onClick={() => { setMenuOpen(false); onDelete(); }} style={menuItemStyle(t, true)}>
                      <window.Icon name="trash" size={20} color={t.coralDk}/>刪除
                    </button>
                  )}
                </div>
              </>
            )}
          </div>
        </div>
      </div>
      )}

      <div style={{ flex: 1, overflowY: 'auto' }}>
        {/* column header — 名稱 | 設定 | 剩餘 */}
        <div style={{ display: 'grid', gridTemplateColumns: MGCOL, gap: MGGAP, alignItems: 'center', padding: '9px 16px', borderBottom: `1px solid ${t.hairline}` }}>
          <span style={{ fontSize: 11, fontWeight: 700, color: t.ink3, letterSpacing: 0.3 }}>名稱</span>
          <span style={{ fontSize: 11, fontWeight: 700, color: t.ink3, letterSpacing: 0.3 }}>條件</span>
          <span style={{ fontSize: 11, fontWeight: 700, color: t.ink3, letterSpacing: 0.3, textAlign: 'right' }}>剩餘</span>
          <span/>
        </div>
        {/* origin row — the universe being screened */}
        <div style={{ display: 'grid', gridTemplateColumns: MGCOL, gap: MGGAP, alignItems: 'center', padding: '12px 16px' }}>
          <span style={{ fontSize: 14.5, fontWeight: 700, color: t.ink, letterSpacing: -0.2 }}>{originLabel}</span>
          <span/>
          <span style={{ fontFamily: '"Plus Jakarta Sans",monospace', fontVariantNumeric: 'tabular-nums', fontSize: 14.5, fontWeight: 700, color: t.ink, textAlign: 'right' }}>{mFmt(originSize)}</span>
          <span/>
        </div>

        {applied.length === 0 && (
          <div style={{ padding: '40px 28px 28px', textAlign: 'center', color: t.ink3, fontSize: 13 }}>尚無條件，點下方「新增條件」加入。</div>
        )}
        {applied.map((item, i) => {
          const filter = window.FILTER_BY_ID[item.filterId];
          if (!filter) return null;
          const isOpen = expand && editKey === item.key;
          const tapRow = () => expand ? setEditKey(k => k === item.key ? null : item.key) : setEditKey(item.key);
          return (
            <React.Fragment key={item.key}>
            <div onClick={tapRow} style={{
              display: 'grid', gridTemplateColumns: MGCOL, gap: MGGAP, alignItems: 'start', padding: '10px 16px',
              cursor: 'pointer', background: isOpen ? t.surfaceAlt : 'transparent', transition: 'background 120ms ease',
            }}>
              <span style={{ display: 'flex', alignItems: 'center', gap: 4, minWidth: 0 }}>
                <span style={{ fontSize: 14.5, fontWeight: 700, color: t.ink, letterSpacing: -0.2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{filter.label}</span>
              </span>
              <span style={{ fontSize: 14, color: t.ink2, fontWeight: 500, lineHeight: 1.3, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>{summarizeFilter(filter, item.value)}</span>
              <span style={{ fontFamily: '"Plus Jakarta Sans",monospace', fontVariantNumeric: 'tabular-nums', fontSize: 14.5, fontWeight: 700, color: t.ink, textAlign: 'right' }}>{mFmt(remaining[i] != null ? remaining[i] : resultN)}</span>
              <button className="cond-remove" onClick={(e) => { e.stopPropagation(); onRemove(item.key); }} title="移除" style={{
                width: 24, height: 24, borderRadius: 999, border: 'none',
                cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
                justifySelf: 'end', alignSelf: 'start', marginTop: 1, padding: 0,
              }}>
                <window.Icon name="x" size={14} strokeWidth={2.1}/>
              </button>
            </div>
            {isOpen && <InlineEditPanel filter={filter} value={item.value} onChange={(val) => onUpdate(item.key, val)}/>}
            </React.Fragment>
          );
        })}
        <div style={{ height: 12 }}/>
      </div>

      {/* footer: 新增條件 — primary full-width button */}
      <div style={{ flexShrink: 0, borderTop: `1px solid ${t.hairline}`, background: t.bg, padding: '12px 16px 22px', display: 'flex', flexDirection: 'column', gap: 10 }}>
        <button onClick={onAdd} style={{
          width: '100%', background: t.ink, color: t.bg, border: 'none', borderRadius: 12, padding: '14px',
          fontSize: 14.5, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit',
          display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
        }}>新增條件</button>
      </div>

      {!expand && editFilter && (
        <CenterDialogConfig filter={editFilter} initial={editItem.value} addLabel="儲存"
          onClose={() => setEditKey(null)}
          onAdd={(val) => { onUpdate(editKey, val); setEditKey(null); }}/>
      )}
    </div>
  );
}

// ── FilterEditSheet — slide-up bottom sheet chrome for the condition editor ─
// Dimmed backdrop + rounded sheet + circular close · centered title · ⋯ menu.
function sheetCircle(t) {
  return {
    flexShrink: 0, width: 52, height: 52, borderRadius: 26, background: t.surface,
    border: `1px solid ${t.hairline}`, display: 'flex', alignItems: 'center', justifyContent: 'center',
    cursor: 'pointer', fontFamily: 'inherit', boxShadow: '0 1px 3px rgba(0,0,0,0.05)',
  };
}
function FilterEditSheet({ title, onClose, onDelete, onShare, onRename, overlay, children }) {
  const t = window.flutterTokens;
  const [closing, setClosing] = uss(false);
  const [menuOpen, setMenuOpen] = uss(false);
  const dismiss = () => { if (closing) return; setClosing(true); setTimeout(() => onClose && onClose(), 260); };
  const hasMenu = onDelete || onShare || onRename;
  return (
    <div onClick={dismiss} style={{ position: 'absolute', inset: 0, zIndex: 9, display: 'flex', flexDirection: 'column', justifyContent: 'flex-end' }}>
      <div onClick={e => e.stopPropagation()} style={{
        position: 'relative', zIndex: 1, height: '100%', background: t.bg,
        display: 'flex', flexDirection: 'column', overflow: 'hidden',
        animation: `${closing ? 'sheetDown' : 'sheetUp'} 340ms cubic-bezier(.22,.7,.25,1) both`,
      }}>
        {/* header */}
        <div style={{ flexShrink: 0, padding: '56px 12px 12px', display: 'flex', alignItems: 'center', gap: 8 }}>
          <button onClick={dismiss} aria-label="關閉" style={{ width: 32, flexShrink: 0, background: 'none', border: 'none', cursor: 'pointer', padding: 6, color: t.ink, display: 'flex', marginLeft: -2 }}>
            <svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M4 4L16 16M16 4L4 16" stroke={t.ink} strokeWidth="2.1" strokeLinecap="round"/></svg>
          </button>
          <div style={{ flex: 1, minWidth: 0, textAlign: 'center', fontFamily: '"Plus Jakarta Sans","Noto Sans TC",sans-serif', fontSize: 17, fontWeight: 700, color: t.ink, letterSpacing: -0.3, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{title}</div>
          <div style={{ width: 32, flexShrink: 0, position: 'relative', display: 'flex', justifyContent: 'flex-end' }}>
            {hasMenu ? (
              <>
                <button onClick={() => setMenuOpen(v => !v)} aria-label="更多" style={{ width: 32, height: 32, borderRadius: 16, background: menuOpen ? t.surfaceAlt : 'none', border: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', color: t.ink }}>
                  <window.Icon name="more" size={22} color={t.ink}/>
                </button>
                {menuOpen && (
                  <>
                    <div onClick={() => setMenuOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 50 }}/>
                    <div className="fs-applemenu" style={{ position: 'absolute', top: 'calc(100% + 6px)', right: 0, zIndex: 51, minWidth: 176, background: 'rgba(250,249,247,0.86)', backdropFilter: 'blur(20px)', WebkitBackdropFilter: 'blur(20px)', borderRadius: 14, boxShadow: '0 12px 40px -8px rgba(31,27,22,0.32)', padding: 0, overflow: 'hidden', animation: 'expandIn 150ms cubic-bezier(.2,.7,.2,1)' }}>
                      {onRename && <button onClick={() => { setMenuOpen(false); onRename(); }} style={menuItemStyle(t)}><window.Icon name="edit" size={20} color={t.ink}/>修改名稱</button>}
                      {onShare && <button onClick={() => { setMenuOpen(false); onShare(); }} style={menuItemStyle(t)}><window.Icon name="share" size={20} color={t.ink}/>分享</button>}
                      {onDelete && <button onClick={() => { setMenuOpen(false); onDelete(); }} style={menuItemStyle(t, true)}><window.Icon name="trash" size={20} color={t.coralDk}/>刪除</button>}
                    </div>
                  </>
                )}
              </>
            ) : null}
          </div>
        </div>
        {/* body */}
        <div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
          {typeof children === 'function' ? children(dismiss) : children}
        </div>
        {/* pushed-in sub-page (e.g. add-condition catalog), covers the whole sheet */}
        {overlay && (
          <div style={{ position: 'absolute', inset: 0, zIndex: 5, background: t.bg, overflow: 'hidden', animation: 'pushIn 300ms cubic-bezier(.2,.7,.2,1)' }}>
            {overlay}
          </div>
        )}
      </div>
    </div>
  );
}

// ── EditFilterFlow — list (edit) ⇄ catalog (add) ─────────────────
function EditFilterFlow({ onClose, onViewResults, resultCount, initialEditFilterId = null, originLabel = '全市場', originSize = 2000, editMode = 'expand', embedded = false }) {
  const [view, setView] = uss('list');   // 'list' | 'catalog'
  const [applied, setApplied] = uss(seedApplied);
  const initialEditKey = uss(() => {
    if (!initialEditFilterId) return null;
    const hit = applied.find(x => x.filterId === initialEditFilterId);
    return hit ? hit.key : null;
  })[0];

  if (view === 'catalog') {
    return (
      <window.CatalogScreen
        accordion={true} mode="dialog" embedded={embedded}
        onBack={() => setView('list')}
        onAddFilter={(filterId, value) => {
          setApplied(a => [...a, { key: newAppliedId(), filterId, value }]);
          setView('list');
        }}/>
    );
  }
  return (
    <EditFilterList
      applied={applied}
      resultBase={resultCount}
      initialEditKey={initialEditKey}
      originLabel={originLabel}
      originSize={originSize}
      editMode={editMode}
      embedded={embedded}
      onRemove={(key) => setApplied(a => a.filter(x => x.key !== key))}
      onUpdate={(key, value) => setApplied(a => a.map(x => x.key === key ? { ...x, value } : x))}
      onAdd={() => setView('catalog')}
      onSave={onViewResults || onClose}
      onBack={onClose}/>
  );
}

Object.assign(window, { CatalogScreen, FullPageConfig, ConfigSheet, CenterDialogConfig, EditFilterList, EditFilterFlow, FilterEditSheet, seedApplied, newAppliedId });
