/* TokenDrawer.jsx — right slide-in editor for a token (or new token) */
const { useState: useStateTD, useMemo: useMemoTD, useEffect: useEffectTD } = React;

const NAME_RE = /^[a-z][a-z0-9]*(\.[a-z0-9][a-z0-9-]*){1,}$/;

/* ===================================================================== *
 *  Guided token-naming slot builder (NEW tokens only).
 *  Assembles a valid dotted token id from tier-specific structured slots,
 *  enforcing the naming grammar:
 *    `.` separates HIERARCHY LEVELS; `-` (kebab) joins words INSIDE a level.
 *  Tier grammars:
 *    primitive: category.family.step          (names the VALUE)
 *    semantic : category.role.variant?.state? (names the INTENT)
 *    component: component.part.property?.state? (names the PART)
 * ===================================================================== */

/* a single level segment: lowercase letters/digits/hyphen, no leading/
   trailing hyphen, no empty interior, no dot. */
const SEG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
function segValid(s){ return SEG_RE.test(s); }

/* slot definitions per tier — `req` slots gate the Add button. */
const TIER_SLOTS = {
  primitive: {
    first: () => tr('category','类别'),
    slots: [
      { key:'family', req:true,  ph:'gray / accent', label:()=>tr('family','族') },
      { key:'step',   req:true,  ph:'500 / md / 200', label:()=>tr('step','档位') },
    ],
    hint: () => tr('Primitive names the value: category.family.step — e.g. color.gray.500, space.400, radius.md.',
                   '原始 token 命名「值」：category.family.step —— 如 color.gray.500、space.400、radius.md。'),
  },
  semantic: {
    first: () => tr('category','类别'),
    slots: [
      { key:'role',    req:true,  ph:'text / bg / accent', label:()=>tr('role','角色') },
      { key:'variant', req:false, ph:'primary / surface', label:()=>tr('variant (optional)','变体（可选）') },
      { key:'state',   req:false, ph:'hover / pressed',    label:()=>tr('state (optional)','状态（可选）') },
    ],
    hint: () => tr('Semantic names the intent: category.role.variant?.state? — e.g. color.text.primary, color.accent.hover.',
                   '语义 token 命名「意图」：category.role.variant?.state? —— 如 color.text.primary、color.accent.hover。'),
  },
  component: {
    first: () => tr('component','组件'),
    slots: [
      { key:'part',     req:true,  ph:'bg / text / border', label:()=>tr('part','部位') },
      { key:'property', req:false, ph:'on / active',          label:()=>tr('property (optional)','属性（可选）') },
      { key:'state',    req:false, ph:'hover / disabled',     label:()=>tr('state (optional)','状态（可选）') },
    ],
    hint: () => tr('Component names the part: component.part.property?.state? — e.g. button.bg, switch.on.bg, tabs.active.border.',
                   '组件 token 命名「部位」：component.part.property?.state? —— 如 button.bg、switch.on.bg、tabs.active.border。'),
  },
};

/* first-segment namespaces already used in the system (nudges reuse). */
function firstSegments(){ return [...new Set(TOKENS.map(t => t.id.split('.')[0]))].sort(); }

/* does a token's BASE source transitively reach a themeAccent? Such tokens already diverge per
   brand via the accent scale, so they are NOT eligible for a separate per-theme pin. */
function resolvesToAccentSource(tk, _seen){
  const seen = _seen || [];
  if (!tk || seen.includes(tk.id)) return false;
  const s = tk.source; if (!s) return false;
  if (s.type === 'themeAccent') return true;
  if (s.type === 'ref') { const nx = TOKEN_MAP[s.ref]; return nx ? resolvesToAccentSource(nx, seen.concat(tk.id)) : false; }
  if (s.type === 'modeRef') { const l = TOKEN_MAP[s.light], d = TOKEN_MAP[s.dark];
    return (l && resolvesToAccentSource(l, seen.concat(tk.id))) || (d && resolvesToAccentSource(d, seen.concat(tk.id))); }
  return false;
}

/* assemble a dotted id from the first segment + ordered slot values
   (trailing empties dropped). */
function assembleId(first, tier, vals){
  const order = TIER_SLOTS[tier].slots.map(s => s.key);
  const segs = [first].concat(order.map(k => (vals[k]||'').trim()));
  // drop trailing empties so optional slots don't leave dangling dots
  while (segs.length && segs[segs.length-1] === '') segs.pop();
  return segs.filter((s,i)=> i===0 || s!=='').join('.');
}

/* soft "this looks like a value, not a purpose" warning for semantic/component.
   non-blocking. */
const PRIMITIVE_COLOR_WORDS = ['gray','grey','black','white','blue','red','green','amber','orange','yellow','purple','violet','indigo','teal','cyan','pink','brown'];
function valuey(tier, first, vals){
  if (tier === 'primitive') return null; // primitives DO name the value — fine.
  const tail = TIER_SLOTS[tier].slots.map(s => (vals[s.key]||'').toLowerCase());
  for (const seg of tail){
    if (!seg) continue;
    if (/#?[0-9a-f]{6}\b/.test(seg) || /#[0-9a-f]{3,8}/.test(seg)) return 'hex';
    if (/\b\d{2,3}\b/.test(seg)) return 'scale';            // bare scale step like 500 / 900
    if (PRIMITIVE_COLOR_WORDS.some(w => seg === w || seg.includes(w))) return 'colorword';
  }
  return null;
}

/* slot builder UI. Owns its own first-segment / slot-value state; lifts the
   assembled id up via onIdChange so it flows into the drawer's `name` state. */
function TokenNameBuilder({ tier, setTier, allowTier, id, onIdChange }) {
  const segs = useMemoTD(() => firstSegments(), []);
  // is the current first segment one of the known namespaces, or custom?
  const firstOfId = (id||'').split('.')[0] || '';
  const [first, setFirst] = useStateTD(() => firstOfId && !segs.includes(firstOfId) ? '__custom__' : firstOfId);
  const [customFirst, setCustomFirst] = useStateTD(() => firstOfId && !segs.includes(firstOfId) ? firstOfId : '');
  const [vals, setVals] = useStateTD({});

  const def = TIER_SLOTS[tier];
  const effFirst = first === '__custom__' ? customFirst.trim() : first;

  // recompute assembled id whenever any slot input changes, and lift up.
  useEffectTD(() => {
    onIdChange(assembleId(effFirst, tier, vals));
    // eslint-disable-next-line
  }, [effFirst, tier, JSON.stringify(vals)]);

  const setSlot = (k,v) => setVals(prev => ({ ...prev, [k]: v.toLowerCase().replace(/\s+/g,'-') }));

  const firstValid = !!effFirst && segValid(effFirst);
  const warn = valuey(tier, effFirst, vals);

  const tierOpts = [
    { value:'primitive', label:tr('Primitive','原始') },
    { value:'semantic',  label:tr('Semantic','语义') },
    { value:'component', label:tr('Component','组件') },
  ];

  return (
    <div className="flex flex-col gap-3">
      {/* tier picker */}
      <div className="flex items-center justify-between gap-2 flex-wrap">
        <Segmented
          options={allowTier ? tierOpts : tierOpts.filter(o => o.value==='primitive')}
          value={tier}
          onChange={t => { if (allowTier || t==='primitive') setTier(t); }}
        />
        <span className="text-[11px] text-ink3">{def.first()} · {def.slots.map(s=>s.key).join(' · ')}</span>
      </div>

      {/* first segment: known namespace select + "+ new…" */}
      <div className="grid gap-2" style={{ gridTemplateColumns:'minmax(0,1fr) minmax(0,1fr)' }}>
        <div className="flex flex-col gap-1">
          <label className="text-[11px] text-ink3">{def.first()}</label>
          {first === '__custom__' ? (
            <div className="relative">
              <TextInput mono autoFocus value={customFirst}
                onChange={e=>setCustomFirst(e.target.value.toLowerCase().replace(/\s+/g,'-'))}
                placeholder={tr('new-namespace','新命名空间')} />
              <button onClick={()=>{ setFirst(segs[0]||''); setCustomFirst(''); }}
                className="absolute right-2 top-1/2 -translate-y-1/2 text-ink3 hover:text-ink" title={tr('Pick existing','选择已有')}>
                <Icon name="close" size={13} />
              </button>
            </div>
          ) : (
            <div className="relative">
              <select value={first} onChange={e=>{ if(e.target.value==='__custom__'){ setFirst('__custom__'); } else setFirst(e.target.value); }}
                className="w-full h-8 bg-sunken border border-line rounded-[8px] pl-2.5 pr-7 text-[13px] font-mono text-ink focus-ring focus:border-line2 appearance-none cursor-pointer">
                {!firstOfId && <option value="">{tr('choose…','选择…')}</option>}
                {segs.map(s => <option key={s} value={s}>{s}</option>)}
                <option value="__custom__">{tr('+ new…','+ 新建…')}</option>
              </select>
              <Icon name="chevronD" size={13} className="absolute right-2 top-1/2 -translate-y-1/2 text-ink3 pointer-events-none" />
            </div>
          )}
        </div>
        {/* first remaining (required) slot sits beside the namespace */}
        {def.slots[0] && (
          <SlotInput slot={def.slots[0]} value={vals[def.slots[0].key]||''} onChange={v=>setSlot(def.slots[0].key, v)} />
        )}
      </div>

      {/* remaining slots */}
      {def.slots.length > 1 && (
        <div className="grid gap-2" style={{ gridTemplateColumns:`repeat(${def.slots.length-1}, minmax(0,1fr))` }}>
          {def.slots.slice(1).map(slot => (
            <SlotInput key={slot.key} slot={slot} value={vals[slot.key]||''} onChange={v=>setSlot(slot.key, v)} />
          ))}
        </div>
      )}

      {/* tier grammar hint */}
      <p className="text-[11px] text-ink3 leading-snug">{def.hint()}</p>

      {/* live preview */}
      <div className="rounded-[10px] border border-line bg-sunken px-3 py-2.5 flex flex-col gap-1">
        <span className="text-[10.5px] uppercase tracking-wide text-ink3">{tr('Token id','Token id')}</span>
        <span className="font-mono text-[15px] text-ink leading-tight break-all">
          {id ? id : <span className="text-ink3">{def.first()}.{def.slots.filter(s=>s.req).map(s=>s.key).join('.')}</span>}
        </span>
        {id && <span className="font-mono text-[11px] text-ink3 break-all">{'--' + id.replace(/\./g,'-')}</span>}
      </div>

      {/* soft value-leak warning (semantic / component) */}
      {warn && firstValid && (
        <div className="flex items-start gap-2 rounded-[8px] px-2.5 py-2 text-[11.5px]"
          style={{ background:'color-mix(in srgb, var(--warning) 11%, transparent)', color:'var(--warning)' }}>
          <Icon name="warningTri" size={13} className="mt-0.5 shrink-0" />
          <span>{tr('Semantic names should describe purpose, not the value — e.g. color.text.primary, not color.text.gray900.',
                    '语义命名应描述「用途」而非「值」—— 如 color.text.primary，而不是 color.text.gray900。')}</span>
        </div>
      )}
    </div>
  );
}

/* one kebab-validated slot text input with label + inline error. */
function SlotInput({ slot, value, onChange }) {
  const touched = value.length > 0;
  const bad = touched && !segValid(value);
  return (
    <div className="flex flex-col gap-1 min-w-0">
      <label className="text-[11px] text-ink3 truncate">{slot.label()}{slot.req && <span className="text-danger"> *</span>}</label>
      <TextInput mono value={value} onChange={e=>onChange(e.target.value)} placeholder={slot.ph}
        className={cx(bad && 'border-danger')} />
      {bad && <span className="text-[10.5px] text-danger">{tr('a–z, 0–9, “-” only','仅限 a–z、0–9、“-”')}</span>}
    </div>
  );
}

/* ---- reference picker with live resolve + cycle prevention ---- */
function RefPicker({ currentId, kind, value, onPick, ctx }) {
  const [q, setQ] = useStateTD('');
  const [open, setOpen] = useStateTD(false);
  const candidates = useMemoTD(() => {
    return TOKENS.filter(tk => tk.kind === kind && tk.id !== currentId && tk.status !== 'deprecated')
      .map(tk => ({ tk, cycles: wouldCycle(currentId, tk.id, ctx.mode) }))
      .filter(({ tk }) => !q || tk.id.toLowerCase().includes(q.toLowerCase()))
      .sort((a,b) => (a.cycles?1:0)-(b.cycles?1:0) || a.tk.layer.localeCompare(b.tk.layer));
  }, [q, currentId, kind, ctx.mode]);

  const resolved = value ? resolve(value, ctx) : null;

  return (
    <div className="flex flex-col gap-2">
      <button onClick={()=>setOpen(o=>!o)}
        className="w-full h-9 flex items-center justify-between bg-sunken border border-line rounded-[8px] px-2.5 text-[13px] focus-ring hover:border-line2 transition">
        {value ? (
          <span className="inline-flex items-center gap-2 min-w-0">
            {resolved && resolved.value && kind==='color' && <Swatch color={resolved.value} size={16} />}
            <span className="font-mono text-accent truncate">{'{'+value+'}'}</span>
          </span>
        ) : <span className="text-ink3">{tr('Select a token to reference…','选择要引用的 token…')}</span>}
        <Icon name="chevronD" size={14} className="text-ink3" />
      </button>

      {value && resolved && (
        <div className="flex items-center gap-2 px-1 text-[11.5px] text-ink3">
          <span>{tr('Resolves to','解析为')}</span>
          {kind==='color' && resolved.value && <Swatch color={resolved.value} size={14} />}
          <span className="font-mono text-ink">{fmtResolved(resolved)}</span>
          {resolved.cycle && <span className="text-danger inline-flex items-center gap-1"><Icon name="warningTri" size={12}/>{tr('cycle','循环')}</span>}
        </div>
      )}

      {open && (
        <div className="border border-line rounded-[12px] bg-panel overflow-hidden" style={{ animation:'fadeIn 120ms ease' }}>
          <div className="p-2 border-b border-line">
            <SearchInput value={q} onChange={setQ} placeholder={tr('Search tokens…','搜索 token…')} size="sm" />
          </div>
          <div className="max-h-[240px] overflow-y-auto ds-scroll py-1">
            {candidates.length === 0 && <p className="px-3 py-4 text-[12px] text-ink3 text-center">{tr('No matching','没有匹配的')} {kind} {tr('tokens.','token。')}</p>}
            {candidates.map(({ tk, cycles }) => {
              const r = resolve(tk.id, ctx);
              return (
                <button key={tk.id} disabled={cycles}
                  onClick={()=>{ onPick(tk.id); setOpen(false); setQ(''); }}
                  className={cx('w-full flex items-center gap-2.5 px-3 h-9 text-left transition-colors',
                    cycles ? 'opacity-45 cursor-not-allowed' : 'hover:bg-hover',
                    value===tk.id && 'bg-accentWeak')}>
                  {tk.kind==='color' ? <Swatch color={r.value} size={16} /> : <KindPreview token={tk} ctx={ctx} size={16} />}
                  <span className="font-mono text-[12px] text-ink truncate flex-1">{tk.id}</span>
                  <span className="text-[10px] uppercase tracking-wide text-ink3">{tk.layer}</span>
                  {cycles && <span className="text-[10px] text-danger inline-flex items-center gap-0.5"><Icon name="warningTri" size={11}/>{tr('cycle','循环')}</span>}
                  {value===tk.id && !cycles && <Icon name="check" size={14} className="text-accent" />}
                </button>
              );
            })}
          </div>
        </div>
      )}
    </div>
  );
}
function fmtResolved(r){
  if (r.cycle) return tr('— unresolved —','— 无法解析 —');
  if (r.kind==='spacing'||r.kind==='radius') return r.value+'px';
  if (r.kind==='type') return r.value+'px / '+(r.lh||'auto');
  return String(r.value);
}

/* ---- value editors ---- */
function ColorValueEditor({ value, onChange }) {
  return (
    <div className="flex items-center gap-2">
      <label className="relative w-9 h-9 rounded-[8px] overflow-hidden border border-line2 checker cursor-pointer shrink-0">
        <span className="absolute inset-0" style={{ background:value }} />
        <input type="color" value={toHex(value)} onChange={e=>onChange(e.target.value.toUpperCase())} className="absolute inset-0 opacity-0 cursor-pointer" />
      </label>
      <TextInput mono value={value} onChange={e=>onChange(e.target.value)} />
    </div>
  );
}
function toHex(v){ return /^#([0-9a-f]{6})$/i.test(v) ? v : '#000000'; }

function NumberValueEditor({ value, unit='px', onChange }) {
  return (
    <div className="flex items-center gap-2">
      <div className="relative flex-1">
        <input type="number" value={value} onChange={e=>onChange(Number(e.target.value))}
          className="w-full h-8 bg-sunken border border-line rounded-[8px] pl-2.5 pr-9 text-[13px] font-mono text-ink focus-ring focus:border-line2" />
        <span className="absolute right-2.5 top-1/2 -translate-y-1/2 text-[12px] text-ink3 font-mono">{unit}</span>
      </div>
      <input type="range" min="0" max="48" value={value} onChange={e=>onChange(Number(e.target.value))} className="flex-1 accent-[var(--accent)]" />
    </div>
  );
}

function TypeValueEditor({ size, lh, onChange }) {
  return (
    <div className="grid grid-cols-2 gap-2">
      <Field label={tr('Size','字号')}>
        <div className="relative">
          <input type="number" value={size} onChange={e=>onChange({ size:Number(e.target.value), lh })} className="w-full h-8 bg-sunken border border-line rounded-[8px] pl-2.5 pr-8 text-[13px] font-mono text-ink focus-ring" />
          <span className="absolute right-2.5 top-1/2 -translate-y-1/2 text-[12px] text-ink3 font-mono">px</span>
        </div>
      </Field>
      <Field label={tr('Line height','行高')}>
        <div className="relative">
          <input type="number" value={lh} onChange={e=>onChange({ size, lh:Number(e.target.value) })} className="w-full h-8 bg-sunken border border-line rounded-[8px] pl-2.5 pr-8 text-[13px] font-mono text-ink focus-ring" />
          <span className="absolute right-2.5 top-1/2 -translate-y-1/2 text-[12px] text-ink3 font-mono">px</span>
        </div>
      </Field>
    </div>
  );
}

function ShadowValueEditor({ value, onChange }) {
  const p = parseShadow(value);
  const set = (k,v) => onChange(buildShadow({ ...p, [k]:v }));
  return (
    <div className="flex flex-col gap-3">
      <div className="h-20 rounded-[12px] bg-sunken border border-line flex items-center justify-center">
        <span className="w-12 h-12 rounded-[8px] bg-panel border border-line" style={{ boxShadow:value }} />
      </div>
      {[['x',tr('Offset X','水平偏移'),-12,12],['y',tr('Offset Y','垂直偏移'),-12,24],['blur',tr('Blur','模糊'),0,48],['spread',tr('Spread','扩展'),-8,12]].map(([k,label,min,max])=>(
        <div key={k} className="flex items-center gap-2">
          <span className="w-16 text-[12px] text-ink2">{label}</span>
          <input type="range" min={min} max={max} value={p[k]} onChange={e=>set(k,Number(e.target.value))} className="flex-1 accent-[var(--accent)]" />
          <span className="w-9 text-right font-mono text-[12px] text-ink">{p[k]}</span>
        </div>
      ))}
      <div className="flex items-center gap-2">
        <span className="w-16 text-[12px] text-ink2">{tr('Opacity','不透明度')}</span>
        <input type="range" min="0" max="40" value={Math.round(p.alpha*100)} onChange={e=>set('alpha',Number(e.target.value)/100)} className="flex-1 accent-[var(--accent)]" />
        <span className="w-9 text-right font-mono text-[12px] text-ink">{Math.round(p.alpha*100)}%</span>
      </div>
    </div>
  );
}
function parseShadow(v){
  const m = (v||'').match(/(-?\d+)px\s+(-?\d+)px\s+(-?\d+)px(?:\s+(-?\d+)px)?\s+rgba\(0,0,0,([\d.]+)\)/);
  if(!m) return { x:0,y:4,blur:14,spread:0,alpha:0.1 };
  return { x:+m[1], y:+m[2], blur:+m[3], spread:m[4]?+m[4]:0, alpha:+m[5] };
}
function buildShadow(p){ return `${p.x}px ${p.y}px ${p.blur}px ${p.spread?p.spread+'px ':''}rgba(0,0,0,${p.alpha})`; }

/* ---- main drawer ---- */
function TokenDrawer({ token, isNew, ctx, theme, onClose, onSave, onDeprecate }) {
  const live = token;
  // active brand theme this edit is scoped to (for "this theme only" pins). Falls back to ctx.theme.
  const activeTheme = theme || (ctx && ctx.theme) || THEMES[0];
  // is the active edit a per-brand PIN ('theme') or a shared base edit ('all', the default)?
  // accent-derived tokens can't be pinned (themes already diverge on accent via the accent scale).
  const isAccentToken = !!live && resolvesToAccentSource(live);
  // when an existing token is already pinned by the active theme, seed the editor from the pin.
  const activePin = (!isNew && live && activeTheme && activeTheme.overrides) ? activeTheme.overrides[live.id] : null;
  // the source the editor seeds from: the active theme's pin if present, else the base source.
  const seedSource = activePin || live?.source;
  const [scope, setScope] = useStateTD(activePin && !isAccentToken ? 'theme' : 'all');
  const [name, setName] = useStateTD(live?.id || '');
  const [desc, setDesc] = useStateTD(live?.description || '');
  const [tags, setTags] = useStateTD(live?.tags || []);
  const initialMode = seedSource?.type === 'value' || seedSource?.type === 'themeAccent' ? 'value' : 'ref';
  const [valueMode, setValueMode] = useStateTD(initialMode);
  const [val, setVal] = useStateTD(() => initVal(live, activeTheme, ctx));
  const [refTarget, setRefTarget] = useStateTD(seedSource && initialMode==='ref' ? nextRef(seedSource, ctx.mode) : null);
  const [confirmDep, setConfirmDep] = useStateTD(false);
  const [depsOpen, setDepsOpen] = useStateTD(true);
  const [saved, setSaved] = useStateTD(false);
  // NEW-token naming: guided slot builder vs. raw free-text escape hatch.
  // tier is seeded from the value/ref mode (value→primitive; ref→semantic),
  // and the user may override it. allowTier is false in value mode (value ⇒ primitive).
  const [tier, setTier] = useStateTD(initialMode === 'value' ? 'primitive' : 'semantic');
  const [rawMode, setRawMode] = useStateTD(false);

  const kind = live?.kind || 'color';
  const nameValid = NAME_RE.test(name);
  const nameDup = isNew && !!TOKEN_MAP[name];

  // keep tier coherent with the value/ref mode: a direct value ⇒ primitive;
  // switching to a reference moves a primitive tier into semantic (user can
  // still pick component). Only applies to NEW tokens with the builder open.
  useEffectTD(() => {
    if (!isNew || rawMode) return;
    if (valueMode === 'value' && tier !== 'primitive') setTier('primitive');
    if (valueMode === 'ref' && tier === 'primitive') setTier('semantic');
  }, [valueMode, isNew, rawMode]);

  const previewToken = useMemoTD(() => ({
    id: name || 'new.token', kind,
    source: valueMode === 'ref'
      ? { type:'ref', ref: refTarget }
      : kind==='color' ? { type:'value', value: val.color }
      : kind==='type' ? { type:'value', value: val.size, lh: val.lh }
      : kind==='shadow' ? { type:'value', value: val.shadow }
      : { type:'value', value: val.num },
  }), [name, kind, valueMode, refTarget, val]);

  const livePreview = useMemoTD(() => {
    // resolve preview token in an isolated map
    const tmp = { ...TOKEN_MAP, [previewToken.id]: previewToken };
    const saved = window.TOKEN_MAP;
    window.TOKEN_MAP = tmp;
    // build a preview ctx whose active theme reflects the pending edit:
    //  - 'theme' scope: pin previewToken.source as the override for this id (don't disturb others)
    //  - 'all' scope: drop any real pin on this id so we preview the edited base source
    let pctx = ctx;
    if (!isNew && ctx && ctx.theme) {
      const baseOv = ctx.theme.overrides || {};
      const ov = { ...baseOv };
      if (scope === 'theme' && !isAccentToken) ov[previewToken.id] = previewToken.source;
      else delete ov[previewToken.id];
      pctx = { ...ctx, theme: { ...ctx.theme, overrides: ov } };
    }
    const r = resolve(previewToken.id, pctx);
    window.TOKEN_MAP = saved;
    return r;
  }, [previewToken, ctx, scope, isAccentToken, isNew]);

  const deps = useMemoTD(() => live ? dependentsOf(live.id) : [], [live]);
  const depTokens = deps.filter(id => TOKEN_MAP[id]?.layer !== 'component');
  const depComponents = deps.filter(id => TOKEN_MAP[id]?.layer === 'component');

  function doSave(){
    // scope==='theme' writes a per-brand pin into activeTheme.overrides; 'all' edits the base token.
    const effScope = (!isNew && scope==='theme' && !isAccentToken) ? 'theme' : 'all';
    onSave && onSave({ id:name, description:desc, tags, valueMode, val, refTarget, kind, isNew,
      scope: effScope, themeId: activeTheme && activeTheme.id });
    setSaved(true);
    setTimeout(()=>{ setSaved(false); onClose(); }, 650);
  }

  return (
    <Drawer open={true} onClose={onClose} width={480}>
      {/* header */}
      <div className="flex items-start justify-between px-5 pt-4 pb-3 border-b border-line">
        <div className="min-w-0">
          <div className="flex items-center gap-2 text-[11px] text-ink3 uppercase tracking-wide mb-1">
            <span>{isNew ? tr('New token','新建 token') : tr(live.layer, {primitive:'原始',semantic:'语义',component:'组件'}[live.layer])}</span>
            {!isNew && <span className="text-ink3">·</span>}
            {!isNew && <span>{live.category}</span>}
          </div>
          <h2 className="font-mono text-[15px] text-ink font-medium truncate">{name || 'new.token'}</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">
        {/* live preview */}
        <div className="rounded-[12px] border border-line bg-sunken p-4 flex items-center gap-4">
          <PreviewBig kind={kind} resolved={livePreview} val={val} valueMode={valueMode} />
          <div className="min-w-0 flex-1">
            <div className="text-[11px] text-ink3 uppercase tracking-wide mb-1">{tr('Live preview','实时预览')} · {ctx.theme.name} · {ctx.mode}</div>
            <div className="font-mono text-[14px] text-ink">{fmtResolved(livePreview)}</div>
            {valueMode==='ref' && refTarget && <div className="mt-1.5"><RefChain chain={livePreview.chain} /></div>}
            {livePreview.cycle && <div className="mt-1 text-[12px] text-danger inline-flex items-center gap-1"><Icon name="warningTri" size={12}/>{tr('Circular reference — value can’t resolve.','存在循环引用 —— 无法解析值。')}</div>}
          </div>
        </div>

        {/* name */}
        {isNew && !rawMode ? (
          /* ---- guided slot builder (NEW tokens) ---- */
          <div className="flex flex-col gap-2">
            <div className="flex items-center justify-between gap-2">
              <label className="text-[12px] font-medium text-ink2">{tr('Name','名称')}</label>
              <div className="flex items-center gap-3">
                <span className="text-[11px] text-ink3">
                  {tr('Naming guide','命名规范')}
                  <span className="text-ink3"> · {tr('Content → Guidelines','内容 → 规范')} →</span>
                </span>
                <button onClick={()=>setRawMode(true)}
                  className="text-[11px] text-accent hover:underline inline-flex items-center gap-1">
                  <Icon name="edit" size={11} />{tr('Advanced: type raw id','高级：手动输入 id')}
                </button>
              </div>
            </div>
            <TokenNameBuilder tier={tier} setTier={setTier} allowTier={valueMode==='ref'} id={name} onIdChange={setName} />
            {nameDup && <p className="text-[11.5px] text-danger flex items-center gap-1"><Icon name="warningTri" size={12} />{tr('A token with this name already exists.','已存在同名 token。')}</p>}
          </div>
        ) : (
          /* ---- free-text id: edit mode (existing token, read-only-style) or
                 the NEW-token "advanced" escape hatch ---- */
          <Field label={tr('Name','名称')} hint={!name?tr('Use dot.case, e.g. color.accent.default','使用 dot.case，如 color.accent.default'):undefined}
            error={name && !nameValid ? tr('Use lowercase dot.case (a–z, 0–9, “-”).','请使用小写 dot.case（a–z、0–9、“-”）。') : nameDup ? tr('A token with this name already exists.','已存在同名 token。') : undefined}>
            {isNew && rawMode && (
              <button onClick={()=>setRawMode(false)}
                className="self-start text-[11px] text-accent hover:underline inline-flex items-center gap-1 mb-0.5">
                <Icon name="chevronR" size={11} className="rotate-180" />{tr('Back to guided builder','返回引导构建器')}
              </button>
            )}
            <div className="relative">
              <TextInput mono value={name} onChange={e=>setName(e.target.value)} readOnly={!isNew} placeholder="group.role.variant"
                className={cx(!isNew && 'opacity-70 cursor-default')} />
              {name && nameValid && !nameDup && <Icon name="check" size={15} className="absolute right-2.5 top-1/2 -translate-y-1/2 text-success" />}
            </div>
          </Field>
        )}

        {/* applies-to scope (EDIT mode only, non-accent tokens): edit the shared base, or pin
            this token for the active brand theme only. Accent-derived tokens are excluded —
            they already diverge per brand via the accent scale. */}
        {!isNew && !isAccentToken && canEdit() && (() => {
          const pinners = typeof themesPinning==='function' ? themesPinning(live.id) : [];
          const otherPinners = pinners.filter(t => t.id !== (activeTheme && activeTheme.id));
          return (
            <Field label={tr('Applies to','作用范围')}>
              <div className="flex flex-col gap-2">
                <Segmented value={scope} onChange={setScope} options={[
                  { value:'all',   label:tr('All themes','全部主题') },
                  { value:'theme', label:tr('This theme only','仅此主题') },
                ]} />
                {scope==='theme' ? (
                  <p className="text-[11.5px] text-ink3 leading-snug">
                    {tr('Pins this token for ','为 ')}<b className="text-ink2">{activeTheme && activeTheme.name}</b>
                    {tr(' only — the base stays unchanged and every other theme keeps inheriting it.',' 单独固定该 token —— 基座保持不变，其它主题继续继承。')}
                  </p>
                ) : (
                  <p className="text-[11.5px] text-ink3 leading-snug">
                    {tr('Edits the shared base token — every theme that inherits it updates.','编辑共享的基座 token —— 所有继承它的主题都会更新。')}
                  </p>
                )}
                {pinners.length>0 && (
                  <p className="text-[11.5px] inline-flex items-center gap-1.5" style={{ color:'var(--warning)' }}>
                    <Icon name="branch" size={12} />
                    {tr(`Currently pinned by ${pinners.length} theme${pinners.length>1?'s':''}`,`当前已被 ${pinners.length} 个主题固定`)}
                    {otherPinners.length>0 && <span className="text-ink3">· {otherPinners.map(t=>t.name).join(', ')}</span>}
                  </p>
                )}
              </div>
            </Field>
          );
        })()}

        {/* value source toggle */}
        <Field label={tr('Value','值')}>
          <div className="flex flex-col gap-3">
            <div className="flex gap-2">
              {[['value',tr('Direct value','直接值'),'sliders'],['ref',tr('Reference token','引用 token'),'link']].map(([m,label,ic])=>(
                <button key={m} onClick={()=>setValueMode(m)}
                  className={cx('flex-1 flex items-center gap-2 h-9 px-3 rounded-[8px] border text-[13px] font-medium transition',
                    valueMode===m ? 'border-accent bg-accentWeak text-accent' : 'border-line bg-sunken text-ink2 hover:text-ink')}>
                  <span className={cx('w-3.5 h-3.5 rounded-full border-2 flex items-center justify-center shrink-0', valueMode===m?'border-accent':'border-ink3')}>
                    {valueMode===m && <span className="w-1.5 h-1.5 rounded-full bg-accent" />}
                  </span>
                  <Icon name={ic} size={14} />{label}
                </button>
              ))}
            </div>

            {valueMode==='value' ? (
              <div>
                {kind==='color' && <ColorValueEditor value={val.color} onChange={c=>setVal(v=>({...v,color:c}))} />}
                {(kind==='spacing'||kind==='radius') && <NumberValueEditor value={val.num} onChange={n=>setVal(v=>({...v,num:n}))} />}
                {kind==='type' && <TypeValueEditor size={val.size} lh={val.lh} onChange={o=>setVal(v=>({...v,...o}))} />}
                {kind==='shadow' && <ShadowValueEditor value={val.shadow} onChange={s=>setVal(v=>({...v,shadow:s}))} />}
              </div>
            ) : (
              <RefPicker currentId={name||'__new__'} kind={kind} value={refTarget} onPick={setRefTarget} ctx={ctx} />
            )}
          </div>
        </Field>

        {/* description */}
        <Field label={tr('Description','描述')}>
          <textarea value={desc} onChange={e=>setDesc(e.target.value)} rows={2} placeholder={tr('What is this token for?','这个 token 用来做什么?')}
            className="w-full bg-sunken border border-line rounded-[8px] px-2.5 py-2 text-[13px] text-ink focus-ring resize-none" />
        </Field>

        {/* tags */}
        <Field label={tr('Tags','标签')}>
          <TagInput value={tags} onChange={setTags} disabled={!canEdit()} placeholder={tr('Add tag…','添加标签…')} mono />
        </Field>

        {/* reverse dependencies */}
        {!isNew && (
          <div className="rounded-[12px] border border-line overflow-hidden">
            <button onClick={()=>setDepsOpen(o=>!o)} className="w-full flex items-center justify-between px-3.5 h-11 hover:bg-hover transition">
              <span className="flex items-center gap-2 text-[13px] text-ink">
                <Icon name="branch" size={14} className="text-ink2" />
                {tr('Referenced by','被引用：')} <b className="font-semibold">{depTokens.length}</b> {tr('tokens,','个 token、')} <b className="font-semibold">{depComponents.length}</b> {tr('components','个组件')}
              </span>
              <Icon name={depsOpen?'chevronD':'chevronR'} size={14} className="text-ink3" />
            </button>
            {depsOpen && deps.length>0 && (
              <div className="border-t border-line divide-y divide-[color:var(--border)]">
                {deps.map(id => {
                  const dt = TOKEN_MAP[id];
                  const r = resolve(id, ctx);
                  return (
                    <div key={id} className="flex items-center gap-2.5 px-3.5 h-9">
                      {dt.kind==='color' ? <Swatch color={r.value} size={14}/> : <KindPreview token={dt} ctx={ctx} size={14}/>}
                      <span className="font-mono text-[12px] text-ink truncate flex-1">{id}</span>
                      <span className="text-[10px] uppercase tracking-wide text-ink3">{dt.layer}</span>
                    </div>
                  );
                })}
              </div>
            )}
            {depsOpen && deps.length===0 && <div className="border-t border-line px-3.5 py-3 text-[12px] text-ink3">{tr('Nothing references this token yet.','暂时没有 token 引用它。')}</div>}
          </div>
        )}
      </div>

      {/* footer */}
      <div className="border-t border-line px-5 py-3 flex items-center justify-between gap-2">
        <div>
          {!isNew && live.status!=='deprecated' && canEdit() && (
            confirmDep
              ? <ConfirmInline open={true} label={tr('Deprecate this token?','废弃该 token?')} onConfirm={()=>{ onDeprecate(live.id); setConfirmDep(false); onClose(); }} onCancel={()=>setConfirmDep(false)} />
              : <Button variant="danger" size="md" icon="trash" onClick={()=>setConfirmDep(true)}>{tr('Deprecate','废弃')}</Button>
          )}
          {!isNew && live.status==='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>}
        </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()||!nameValid||nameDup||(valueMode==='ref'&&(!refTarget||livePreview.cycle))} onClick={doSave}>
            {saved ? tr('Saved','已保存') : tr('Save draft','保存草稿')}
          </Button>
        </div>
      </div>
    </Drawer>
  );
}

function initVal(live, activeTheme, ctx){
  const base = { color:'#0B6BCB', num:8, size:14, lh:20, shadow:'0 4px 14px rgba(0,0,0,0.10)' };
  if(!live) return base;
  // resolve under the active theme/mode so a per-brand pin seeds from its own resolved value;
  // for direct-value sources, prefer the literal stored value (pin first, then base).
  const theme = activeTheme || (ctx && ctx.theme) || THEMES[0];
  const mode = (ctx && ctx.mode) || 'light';
  const pin = theme && theme.overrides && theme.overrides[live.id];
  const src = pin || live.source;
  const r = resolve(live.id, { theme, mode });
  if(live.kind==='color' && src.type==='value') base.color = src.value;
  else if(live.kind==='color') base.color = r.value || base.color;
  if((live.kind==='spacing'||live.kind==='radius')) base.num = typeof r.value==='number'?r.value:base.num;
  if(live.kind==='type'){ base.size = (src.type==='value'?src.value:r.value)||base.size; base.lh = (src.type==='value'?src.lh:r.lh)||base.lh; }
  if(live.kind==='shadow'){ base.shadow = (src.type==='value'?src.value:r.value)||base.shadow; }
  return base;
}

function PreviewBig({ kind, resolved, val, valueMode }) {
  const v = resolved.value;
  if (kind==='color') return <Swatch color={v} size={56} radius={10} />;
  if (kind==='spacing'||kind==='radius') {
    if (kind==='radius') return <span className="w-14 h-14 bg-accentWeak border-2 border-accent" style={{ borderRadius:v }} />;
    return <span className="w-14 h-14 flex items-center"><span className="h-3 rounded bg-accent" style={{ width:Math.min(v*2,56) }} /></span>;
  }
  if (kind==='type') return <span className="w-14 h-14 flex items-end justify-center text-ink font-semibold leading-none" style={{ fontSize:Math.min(v,40) }}>Aa</span>;
  if (kind==='shadow') return <span className="w-12 h-12 m-1 rounded-[8px] bg-panel border border-line" style={{ boxShadow:v }} />;
  return null;
}

Object.assign(window, { TokenDrawer });
