// filter-widgets.jsx — SDUI widget vocabulary + layout-tree renderer.
// Widgets: input · segmented · dropdown · toggle · checkbox · date_picker · metric
// Composites: RangeField (min/max), MetricPicker (form-in-form). The FilterForm
// walks layout.compact.nodes (ref / group / when) and renders the matching widget.

const { useState: uS, useMemo: uM } = React;

// ── form value context (avoids prop drilling through group/when) ──
const FormCtx = React.createContext(null);

// suffix display from unit
const fmtNum = (n) => (n == null || n === '' ? '' : Number(n).toLocaleString('en-US'));

// ── Field label ──────────────────────────────────────────────────
function FieldLabel({ children, required }) {
  const t = window.flutterTokens;
  return (
    <div style={{ fontSize: 12, fontWeight: 600, color: t.ink2, marginBottom: 7, display: 'flex', alignItems: 'center', gap: 4 }}>
      <span>{children}</span>
      {required && <span style={{ color: t.coralDk, fontSize: 12 }}>•</span>}
    </div>
  );
}

// ── input (number) ───────────────────────────────────────────────
function NumberInput({ value, onChange, field, placeholder, stepper }) {
  const t = window.flutterTokens;
  const set = (raw) => {
    if (raw === '' || raw == null) return onChange(null);
    onChange(Number(raw));
  };
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 6,
      background: t.surfaceAlt, borderRadius: 10, padding: '0 11px',
      border: `1px solid ${t.hairline}`, height: 44, boxSizing: 'border-box',
    }}>
      <input
        type="text" inputMode="decimal"
        value={value == null ? '' : value}
        placeholder={placeholder || (field.range && field.range.min != null ? String(field.range.min) : '不限')}
        onChange={(e) => { const x = e.target.value.replace(/[^\d.\-]/g, ''); set(x); }}
        style={{
          flex: 1, minWidth: 0, width: 0, textAlign: 'left',
          fontFamily: '"Plus Jakarta Sans", monospace', fontVariantNumeric: 'tabular-nums',
          fontSize: 16, fontWeight: 700, color: t.ink,
          background: 'transparent', border: 'none', outline: 'none',
        }} />
      {field.unit && <span style={{ fontSize: 12.5, color: t.ink3, fontWeight: 600, flexShrink: 0 }}>{field.unit}</span>}
    </div>
  );
}
function stepBtn(t) {
  return { width: 32, height: 32, borderRadius: 8, background: t.surface, color: t.ink, border: `1px solid ${t.hairline}`, cursor: 'pointer', fontSize: 17, fontWeight: 600, fontFamily: 'inherit', flexShrink: 0, lineHeight: 1 };
}

// ── composite #1 : min / max range ───────────────────────────────
function RangeField({ minField, maxField }) {
  const t = window.flutterTokens;
  const ctx = React.useContext(FormCtx);
  const min = ctx.value.min, max = ctx.value.max;
  const filled = (min != null && min !== '') || (max != null && max !== '');
  return (
    <div>
      <FieldLabel>數值區間</FieldLabel>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
        <div style={{ flex: 1 }}><NumberInput field={minField} value={min} onChange={(x) => ctx.set('min', x)} placeholder="不限"/></div>
        <div style={{ flex: 1 }}><NumberInput field={maxField} value={max} onChange={(x) => ctx.set('max', x)} placeholder="不限"/></div>
      </div>
      <div style={{ marginTop: 7, fontSize: 11, color: filled ? t.ink3 : t.coralDk, fontWeight: 500, display: 'flex', alignItems: 'center', gap: 5 }}>
        <span style={{ width: 4, height: 4, borderRadius: 2, background: filled ? t.ink3 : t.coralDk, display: 'inline-block' }}/>
        {filled ? '單邊留空表示不限' : '至少填一邊'}
      </div>
    </div>
  );
}

// ── segmented (choice, ≤3 single) ────────────────────────────────
function Segmented({ field, value, onChange }) {
  const t = window.flutterTokens;
  const opts = field.options.values;
  return (
    <div style={{ display: 'flex', gap: 4, background: t.surfaceAlt, borderRadius: 11, padding: 3, border: `1px solid ${t.hairline}` }}>
      {opts.map(o => {
        const active = value === o.value;
        return (
          <button key={o.value} onClick={() => onChange(o.value)} style={{
            flex: 1, background: active ? t.surface : 'transparent', border: 'none', borderRadius: 8,
            padding: '8px 6px', fontSize: 13, fontWeight: active ? 700 : 500, color: active ? t.ink : t.ink2,
            cursor: 'pointer', fontFamily: 'inherit',
            boxShadow: active ? '0 1px 2px rgba(31,27,22,0.10)' : 'none', transition: 'all 140ms ease',
          }}>{o.label}</button>
        );
      })}
    </div>
  );
}

// ── checkbox (boolean-set, rendered as toggle chips) ─────────────
function ChipMulti({ field, value, onChange }) {
  const t = window.flutterTokens;
  const arr = value || [];
  const toggle = (vv) => arr.includes(vv) ? onChange(arr.filter(x => x !== vv)) : onChange([...arr, vv]);
  return (
    <div style={{ display: 'flex', flexWrap: 'wrap', gap: 7 }}>
      {field.options.values.map(o => {
        const active = arr.includes(o.value);
        return (
          <button key={o.value} onClick={() => toggle(o.value)} style={{
            display: 'inline-flex', alignItems: 'center', gap: 5,
            background: active ? t.ink : t.surfaceAlt, color: active ? t.bg : t.ink2,
            border: `1px solid ${active ? t.ink : t.hairline}`, borderRadius: 999,
            padding: '8px 13px', fontSize: 13, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit',
          }}>
            {active && <window.Icon name="check" size={13} color={t.bg}/>}
            {o.label}
          </button>
        );
      })}
    </div>
  );
}

// ── toggle (boolean) ─────────────────────────────────────────────
function BoolToggle({ value, onChange }) {
  const t = window.flutterTokens;
  return (
    <button onClick={() => onChange(!value)} style={{
      width: 48, height: 28, borderRadius: 14, background: value ? t.mintDk : t.surfaceAlt,
      border: 'none', cursor: 'pointer', position: 'relative', transition: 'background 180ms', padding: 0,
    }}>
      <div style={{ position: 'absolute', top: 3, left: value ? 23 : 3, width: 22, height: 22, borderRadius: 11, background: '#fff', boxShadow: '0 1px 3px rgba(0,0,0,0.2)', transition: 'left 180ms cubic-bezier(.2,.7,.2,1)' }}/>
    </button>
  );
}

// ── date_picker ──────────────────────────────────────────────────
function DateField({ value, onChange }) {
  const t = window.flutterTokens;
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 8, background: t.surfaceAlt, borderRadius: 10, padding: '0 12px', border: `1px solid ${t.hairline}`, height: 44 }}>
      <window.Icon name="clock" size={16} color={t.ink3}/>
      <input type="date" value={value || ''} onChange={(e) => onChange(e.target.value)} style={{
        flex: 1, background: 'transparent', border: 'none', outline: 'none',
        fontFamily: '"Plus Jakarta Sans", monospace', fontSize: 14, fontWeight: 600, color: value ? t.ink : t.ink3,
      }}/>
    </div>
  );
}

// ── dropdown (choice; single or multi) → anchored dropdown list ──
function Dropdown({ field, value, onChange }) {
  const t = window.flutterTokens;
  const [open, setOpen] = uS(false);
  const multi = !field.single;
  const opts = field.options.kind === 'dynamic'
    ? (window.OPTIONS_BY_SOURCE[field.options.source] || []).map(x => ({ value: x, label: x }))
    : field.options.values;
  const labelFor = (vv) => (opts.find(o => o.value === vv) || {}).label || vv;
  const arr = multi ? (value || []) : [];
  const pick = (vv) => {
    if (multi) { onChange(arr.includes(vv) ? arr.filter(x => x !== vv) : [...arr, vv]); }
    else { onChange(vv); setOpen(false); }
  };

  let display, placeholder = false;
  if (multi) {
    display = arr.length === 0 ? (field.options.source ? '選擇…' : '不限') : arr.slice(0, 2).map(labelFor).join('、') + (arr.length > 2 ? ` +${arr.length - 2}` : '');
    placeholder = arr.length === 0;
  } else {
    display = value == null ? '選擇…' : labelFor(value);
    placeholder = value == null;
  }

  return (
    <div style={{ position: 'relative' }}>
      <button onClick={() => setOpen(o => !o)} style={{
        width: '100%', boxSizing: 'border-box', display: 'flex', alignItems: 'center', gap: 8,
        background: t.surfaceAlt, border: `1px solid ${open ? t.ink3 : t.hairline}`, borderRadius: 10, padding: '0 12px', height: 44,
        cursor: 'pointer', fontFamily: 'inherit', textAlign: 'left',
      }}>
        <span style={{ flex: 1, minWidth: 0, fontSize: 14, fontWeight: 600, color: placeholder ? t.ink3 : t.ink, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{display}</span>
        <span style={{ display: 'inline-flex', transform: open ? 'rotate(180deg)' : 'none', transition: 'transform 160ms ease', color: t.ink3 }}>
          <window.Icon name="chevron-down" size={16} color={t.ink3}/>
        </span>
      </button>
      {open && (
        <>
          <div onClick={() => setOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 30 }}/>
          <div style={{
            position: 'absolute', top: 'calc(100% + 4px)', left: 0, right: 0, zIndex: 40,
            background: t.surface, border: `1px solid ${t.hairline}`, borderRadius: 10,
            boxShadow: '0 12px 28px -10px rgba(31,27,22,0.28)', overflow: 'hidden', maxHeight: 224, overflowY: 'auto',
          }}>
            {opts.map(o => {
              const active = multi ? arr.includes(o.value) : value === o.value;
              return (
                <button key={o.value} onClick={() => pick(o.value)} style={{
                  width: '100%', textAlign: 'left', background: active ? t.surfaceAlt : 'transparent', border: 'none',
                  cursor: 'pointer', fontFamily: 'inherit', padding: '11px 12px', display: 'flex', alignItems: 'center', gap: 9,
                  fontSize: 14, fontWeight: active ? 700 : 500, color: t.ink,
                }}>
                  {multi && (
                    <span style={{ width: 18, height: 18, borderRadius: 5, flexShrink: 0, border: `1.6px solid ${active ? t.ink : t.ink3}`, background: active ? t.ink : 'transparent', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
                      {active && <window.Icon name="check" size={11} color={t.bg}/>}
                    </span>
                  )}
                  <span style={{ flex: 1 }}>{o.label}</span>
                  {!multi && active && <window.Icon name="check" size={16} color={t.ink}/>}
                </button>
              );
            })}
          </div>
        </>
      )}
    </div>
  );
}

function SelectSheet({ field, opts, multi, value, onChange, onClose }) {
  const t = window.flutterTokens;
  const arr = multi ? (value || []) : [];
  const pick = (vv) => {
    if (multi) { onChange(arr.includes(vv) ? arr.filter(x => x !== vv) : [...arr, vv]); }
    else { onChange(vv); onClose(); }
  };
  return (
    <div onClick={onClose} style={{ position: 'absolute', inset: 0, background: 'rgba(31,27,22,0.34)', display: 'flex', alignItems: 'flex-end', zIndex: 80 }}>
      <div onClick={e => e.stopPropagation()} style={{
        width: '100%', background: t.surface, borderRadius: '20px 20px 0 0', padding: '8px 0 14px',
        maxHeight: '74%', display: 'flex', flexDirection: 'column', animation: 'slideUp 220ms cubic-bezier(.2,.7,.2,1)',
      }}>
        <div style={{ width: 36, height: 4, background: t.hairline, borderRadius: 2, margin: '6px auto 10px' }}/>
        <div style={{ padding: '0 20px 10px', display: 'flex', alignItems: 'center' }}>
          <div style={{ flex: 1, fontSize: 16, fontWeight: 700, color: t.ink }}>{field.label}</div>
          {multi && <button onClick={onClose} style={{ background: t.ink, color: t.bg, border: 'none', borderRadius: 999, padding: '6px 14px', fontSize: 13, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit' }}>完成</button>}
        </div>
        <div style={{ overflowY: 'auto', minHeight: 0 }}>
          {opts.map(o => {
            const active = multi ? arr.includes(o.value) : value === o.value;
            return (
              <button key={o.value} onClick={() => pick(o.value)} style={{
                width: '100%', textAlign: 'left', background: active ? t.surfaceAlt : 'transparent', border: 'none',
                cursor: 'pointer', fontFamily: 'inherit', padding: '13px 20px',
                display: 'flex', alignItems: 'center', gap: 10,
                fontSize: 14.5, fontWeight: active ? 700 : 500, color: t.ink,
              }}>
                {multi && (
                  <span style={{ width: 20, height: 20, borderRadius: 6, flexShrink: 0, border: `1.6px solid ${active ? t.ink : t.ink3}`, background: active ? t.ink : 'transparent', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
                    {active && <window.Icon name="check" size={12} color={t.bg}/>}
                  </span>
                )}
                <span style={{ flex: 1 }}>{o.label}</span>
                {!multi && active && <window.Icon name="check" size={17} color={t.ink}/>}
              </button>
            );
          })}
        </div>
      </div>
    </div>
  );
}

// ── composite #3 : metric (form-in-form) ─────────────────────────
function MetricPicker({ field, value, onChange }) {
  const t = window.flutterTokens;
  const [open, setOpen] = uS(false);
  const metrics = window.RANKABLE_METRICS;
  const sel = value ? metrics.find(m => m.kind === value.kind) : null;

  const choose = (m) => {
    const params = {};
    for (const p of m.params) params[p.key] = p.default ?? null;
    onChange({ kind: m.kind, params });
    setOpen(false);
  };
  const setParam = (k, vv) => onChange({ ...value, params: { ...value.params, [k]: vv } });

  return (
    <div>
      {/* metric kind = dropdown */}
      <div style={{ position: 'relative' }}>
        <button onClick={() => setOpen(o => !o)} style={{
          width: '100%', boxSizing: 'border-box', display: 'flex', alignItems: 'center', gap: 8,
          background: t.surfaceAlt, border: `1px solid ${open ? t.ink3 : t.hairline}`, borderRadius: 10, padding: '0 12px', height: 44,
          cursor: 'pointer', fontFamily: 'inherit', textAlign: 'left',
        }}>
          <span style={{ flex: 1, minWidth: 0, fontSize: 14, fontWeight: 600, color: sel ? t.ink : t.ink3, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{sel ? sel.label : '選擇…'}</span>
          <span style={{ display: 'inline-flex', transform: open ? 'rotate(180deg)' : 'none', transition: 'transform 160ms ease', color: t.ink3 }}>
            <window.Icon name="chevron-down" size={16} color={t.ink3}/>
          </span>
        </button>
        {open && (
          <>
            <div onClick={() => setOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 30 }}/>
            <div style={{
              position: 'absolute', top: 'calc(100% + 4px)', left: 0, right: 0, zIndex: 40,
              background: t.surface, border: `1px solid ${t.hairline}`, borderRadius: 10,
              boxShadow: '0 12px 28px -10px rgba(31,27,22,0.28)', overflow: 'hidden', maxHeight: 224, overflowY: 'auto',
            }}>
              {metrics.map(m => {
                const active = value && value.kind === m.kind;
                return (
                  <button key={m.kind} onClick={() => choose(m)} style={{
                    width: '100%', textAlign: 'left', background: active ? t.surfaceAlt : 'transparent', border: 'none',
                    cursor: 'pointer', fontFamily: 'inherit', padding: '11px 12px', display: 'flex', alignItems: 'center', gap: 9,
                    fontSize: 14, fontWeight: active ? 700 : 500, color: t.ink,
                  }}>
                    <span style={{ flex: 1 }}>{m.label}</span>
                    <span style={{ fontSize: 11, color: t.ink3 }}>{m.params.length ? `${m.params.length} 參數` : '—'}</span>
                    {active && <window.Icon name="check" size={16} color={t.ink}/>}
                  </button>
                );
              })}
            </div>
          </>
        )}
      </div>

      {/* when: selected metric carries params → reveal nested sub-form */}
      {sel && sel.params.length > 0 && (
        <div style={{ marginTop: 14, marginLeft: 12, paddingLeft: 14, borderLeft: `2px dashed ${t.hairline}`, display: 'flex', flexDirection: 'column', gap: 14 }}>
          {sel.params.map(p => (
            <SubField key={p.key} field={p} value={value.params[p.key]} onChange={(x) => setParam(p.key, x)}/>
          ))}
        </div>
      )}
    </div>
  );
}

// A self-labelled field used inside the metric sub-form (no layout tree).
function SubField({ field, value, onChange }) {
  return (
    <div>
      <FieldLabel required={field.required}>{field.label}</FieldLabel>
      {field.kind === 'number' && <NumberInput field={field} value={value} onChange={onChange} stepper/>}
      {field.kind === 'choice' && <Dropdown field={field} value={value} onChange={onChange}/>}
    </div>
  );
}

// ── ref → one labelled control ───────────────────────────────────
function FieldRow({ field, widget, label }) {
  const ctx = React.useContext(FormCtx);
  const value = ctx.value[field.key];
  const set = (x) => ctx.set(field.key, x);
  const showLabel = field.kind !== 'metric'; // metric carries its own header

  let control = null;
  switch (widget) {
    case 'input':       control = <NumberInput field={field} value={value} onChange={set} stepper/>; break;
    case 'segmented':   control = <Dropdown field={field} value={value} onChange={set}/>; break;
    case 'dropdown':    control = <Dropdown field={field} value={value} onChange={set}/>; break;
    case 'checkbox':    control = <ChipMulti field={field} value={value} onChange={set}/>; break;
    case 'toggle':      control = <BoolToggle value={value} onChange={set}/>; break;
    case 'date_picker': control = <DateField value={value} onChange={set}/>; break;
    case 'metric':      control = <MetricPicker field={field} value={value} onChange={set}/>; break;
    default:            control = <NumberInput field={field} value={value} onChange={set}/>;
  }
  return (
    <div>
      {showLabel && <FieldLabel required={field.required}>{label ?? field.label}</FieldLabel>}
      {control}
    </div>
  );
}

// ── when → conditional reveal (animated) ─────────────────────────
function WhenWrap({ visible, children }) {
  if (!visible) return null;
  return <div>{children}</div>;
}

// ── layout-tree renderer ─────────────────────────────────────────
function renderNode(node, idx) {
  const filterFields = renderNode._fields;
  const lookup = (k) => filterFields.find(f => f.key === k);

  if (node.kind === 'ref') {
    return <FieldRow key={idx} field={lookup(node.key)} widget={node.widget} label={node.label}/>;
  }
  if (node.kind === 'when') {
    return <WhenNode key={idx} node={node}/>;
  }
  if (node.kind === 'group') {
    const refKeys = node.children.filter(c => c.kind === 'ref').map(c => c.key);
    // composite #1 — a horizontal pair of min/max numbers → one Range component
    if (node.layout === 'horizontal' && refKeys.length === 2 && refKeys.includes('min') && refKeys.includes('max')) {
      return <RangeField key={idx} minField={lookup('min')} maxField={lookup('max')}/>;
    }
    return (
      <div key={idx} style={{ display: 'flex', flexDirection: node.layout === 'horizontal' ? 'row' : 'column', gap: 12, alignItems: node.layout === 'horizontal' ? 'flex-end' : 'stretch' }}>
        {node.children.map((c, i) => (
          <div key={i} style={{ flex: node.layout === 'horizontal' ? 1 : 'initial', minWidth: 0 }}>{renderNode(c, i)}</div>
        ))}
      </div>
    );
  }
  return null;
}

function WhenNode({ node }) {
  const ctx = React.useContext(FormCtx);
  const match = node.equals.includes(ctx.value[node.key]);
  return (
    <WhenWrap visible={match}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
        {node.children.map((c, i) => renderNode(c, i))}
      </div>
    </WhenWrap>
  );
}

// ── FilterForm — drives a whole filter's compact layout ──────────
function FilterForm({ filter, value, setValue, gap = 18 }) {
  const set = React.useCallback((k, v) => setValue(prev => ({ ...prev, [k]: v })), [setValue]);
  const ctx = uM(() => ({ value, set, filter }), [value, set, filter]);
  renderNode._fields = filter.fields;
  return (
    <FormCtx.Provider value={ctx}>
      <div style={{ display: 'flex', flexDirection: 'column', gap }}>
        {filter.layout.nodes.map((n, i) => renderNode(n, i))}
      </div>
    </FormCtx.Provider>
  );
}

Object.assign(window, { FilterForm, FieldLabel });
