/* theme-diff.jsx — theme-comparison engine: divergence detection + color math + the Pill atom.
   A theme only changes the accent scale, so the only tokens that differ across themes are those
   whose source transitively resolves to `themeAccent`. Everything else is provably identical. */
const { useState: useStateTD } = React;

/* ---------- color math (no deps) ---------- */
function tdHexToRgb(h){ h=String(h||'#000').replace('#',''); if(h.length===3) h=h.split('').map(c=>c+c).join(''); const n=parseInt(h,16); return [n>>16&255, n>>8&255, n&255]; }
function tdSrgbToLin(c){ c/=255; return c<=0.04045 ? c/12.92 : Math.pow((c+0.055)/1.055, 2.4); }
function tdRelLum(hex){ const [r,g,b]=tdHexToRgb(hex).map(tdSrgbToLin); return 0.2126*r+0.7152*g+0.0722*b; }
function contrastRatio(a,b){ const L1=tdRelLum(a), L2=tdRelLum(b); const hi=Math.max(L1,L2), lo=Math.min(L1,L2); return +((hi+0.05)/(lo+0.05)).toFixed(2); }
/* CIEDE2000 perceptual color distance */
function deltaE(hex1,hex2){
  if(!hex1||!hex2||hex1[0]!=='#'||hex2[0]!=='#') return 0;
  const lab=h=>{ let [r,g,b]=tdHexToRgb(h).map(tdSrgbToLin);
    let x=(r*0.4124+g*0.3576+b*0.1805)/0.95047, y=(r*0.2126+g*0.7152+b*0.0722), z=(r*0.0193+g*0.1192+b*0.9505)/1.08883;
    const f=t=>t>0.008856?Math.cbrt(t):7.787*t+16/116; x=f(x); y=f(y); z=f(z);
    return [116*y-16, 500*(x-y), 200*(y-z)]; };
  const [L1,a1,b1]=lab(hex1), [L2,a2,b2]=lab(hex2);
  const avgL=(L1+L2)/2, C1=Math.hypot(a1,b1), C2=Math.hypot(a2,b2), avgC=(C1+C2)/2;
  const G=0.5*(1-Math.sqrt(Math.pow(avgC,7)/(Math.pow(avgC,7)+Math.pow(25,7))));
  const a1p=a1*(1+G), a2p=a2*(1+G), C1p=Math.hypot(a1p,b1), C2p=Math.hypot(a2p,b2), avgCp=(C1p+C2p)/2;
  const hf=(x,y)=>{ let a=Math.atan2(y,x)*180/Math.PI; return a<0?a+360:a; };
  const h1p=hf(a1p,b1), h2p=hf(a2p,b2);
  let dhp=h2p-h1p; if(Math.abs(dhp)>180) dhp+=dhp>0?-360:360;
  const dLp=L2-L1, dCp=C2p-C1p, dHp=2*Math.sqrt(C1p*C2p)*Math.sin(dhp/2*Math.PI/180);
  let avgHp=Math.abs(h1p-h2p)>180?(h1p+h2p+360)/2:(h1p+h2p)/2;
  const T=1-0.17*Math.cos((avgHp-30)*Math.PI/180)+0.24*Math.cos(2*avgHp*Math.PI/180)+0.32*Math.cos((3*avgHp+6)*Math.PI/180)-0.20*Math.cos((4*avgHp-63)*Math.PI/180);
  const SL=1+(0.015*Math.pow(avgL-50,2))/Math.sqrt(20+Math.pow(avgL-50,2)), SC=1+0.045*avgCp, SH=1+0.015*avgCp*T;
  const dTheta=30*Math.exp(-Math.pow((avgHp-275)/25,2)), RC=2*Math.sqrt(Math.pow(avgCp,7)/(Math.pow(avgCp,7)+Math.pow(25,7)));
  const RT=-RC*Math.sin(2*dTheta*Math.PI/180);
  return +Math.sqrt(Math.pow(dLp/SL,2)+Math.pow(dCp/SC,2)+Math.pow(dHp/SH,2)+RT*(dCp/SC)*(dHp/SH)).toFixed(1);
}
/* mode-aware backgrounds for the contrast judge (mirror index.html vars) */
function panelBg(mode){ return mode==='dark' ? '#1F1F22' : '#FFFFFF'; }
function onAccent(mode){ return mode==='dark' ? '#0B0B0C' : '#FFFFFF'; }
/* plain-language magnitude band */
function deltaBand(dE){
  if(dE<1)  return { key:'same',   en:'effectively same',   zh:'几乎一致', color:'var(--text-3)' };
  if(dE<2)  return { key:'subtle', en:'barely different',    zh:'细微差异', color:'var(--text-3)' };
  if(dE<10) return { key:'clear',  en:'noticeably different',zh:'明显不同', color:'var(--warning)' };
  return             { key:'strong',en:'clearly different',   zh:'显著不同', color:'var(--accent)' };
}

/* ---------- divergence engine ---------- */
/* token ids that can differ across themes: (a) those whose source transitively reaches a
   themeAccent, UNION (b) any token pinned in ANY theme's `overrides` map (a per-brand override
   makes that token diverge even if its base source is theme-invariant). With all override maps
   empty (today) this returns exactly the accent-derived set, as before. */
function themeVariantTokenIds(){
  const memo={};
  function reach(id, seen){
    if(id in memo) return memo[id];
    if(!id || seen.has(id)) return false;
    seen.add(id);
    const tk=TOKEN_MAP[id]; if(!tk) return false;
    const s=tk.source; let r=false;
    if(s.type==='themeAccent') r=true;
    else if(s.type==='ref') r=reach(s.ref, seen);
    else if(s.type==='modeRef') r=reach(s.light, seen)||reach(s.dark, seen);
    memo[id]=r; return r;
  }
  const set = new Set(TOKENS.filter(t=>reach(t.id, new Set())).map(t=>t.id));
  (typeof THEMES!=='undefined'?THEMES:[]).forEach(th => {
    const ov = th && th.overrides;
    if (ov) Object.keys(ov).forEach(id => { if (TOKEN_MAP[id]) set.add(id); });
  });
  return TOKENS.filter(t=>set.has(t.id)).map(t=>t.id);  // keep TOKENS declaration order
}
/* themes that pin a given token id (for the authoring "N themes pin this" hint + stale flag). */
function themesPinning(tokenId){
  return (typeof THEMES!=='undefined'?THEMES:[]).filter(th => th && th.overrides && th.overrides[tokenId]);
}
/* plain-language role label — strip jargon for management */
function roleLabel(id){
  const M={
    'brand.accent.400': tr('Brand accent · light','品牌强调色 · 浅'),
    'brand.accent.500': tr('Brand accent · base','品牌强调色 · 基准'),
    'brand.accent.600': tr('Brand accent · pressed','品牌强调色 · 按下'),
    'color.accent.default': tr('Accent','强调色'),
    'color.accent.hover': tr('Accent · hover / pressed','强调色 · 悬停'),
    'button.bg': tr('Button fill','按钮填充'),
    'link.text': tr('Link','链接'),
    'switch.on.bg': tr('Switch · on','开关 · 开启'),
    'tabs.active.text': tr('Active tab label','激活标签文字'),
    'tabs.active.border': tr('Active tab underline','激活标签下划线'),
    'progress.fill': tr('Progress fill','进度填充'),
  };
  return M[id] || id;
}
/* one token in one mode: resolved value per theme + ΔE vs baseline + AA flags */
function divergenceRow(tokenId, mode, baselineThemeId){
  const tk=TOKEN_MAP[tokenId];
  const base=THEMES.find(t=>t.id===baselineThemeId)||THEMES[0];
  const baseVal=resolve(tokenId, { theme:base, mode }).value;
  const isColor = tk && tk.kind==='color';
  const cells=THEMES.map(theme=>{
    const val=resolve(tokenId, { theme, mode }).value;
    // is this token PINNED by this theme, and if so, has the base drifted since the pin (stale)?
    const ov = theme.overrides && theme.overrides[tokenId];
    const stale = ov && typeof sourceHash==='function' && ov._basis != null && tk
      ? ov._basis !== sourceHash(tk.source) : false;
    return {
      themeId:theme.id, themeName:theme.name, value:val, isBaseline:theme.id===base.id,
      pinned: !!ov, stale: !!stale,
      dE: isColor ? deltaE(baseVal, val) : 0,
      aaPanel: isColor ? contrastRatio(val, panelBg(mode)) : null,
      aaOnAccent: isColor ? contrastRatio(onAccent(mode), val) : null,
    };
  });
  const maxDE=Math.max(0, ...cells.map(c=>c.dE));
  return { tokenId, kind: tk?tk.kind:'color', role:roleLabel(tokenId), refChain: resolve(tokenId,{theme:base,mode}).chain, cells, maxDE };
}

/* ---------- Pill atom (color-mix inline pill; do NOT reuse StatusTag) ---------- */
function Pill({ tone='neutral', color, icon, children }){
  const c = color || ({ neutral:'var(--text-3)', accent:'var(--accent)', warning:'var(--warning)', danger:'var(--danger)', success:'var(--success)' }[tone]);
  return (
    <span className="inline-flex items-center gap-1 h-[18px] px-1.5 rounded-full text-[10.5px] font-medium whitespace-nowrap"
      style={{ background:'color-mix(in srgb, '+c+' 14%, transparent)', color:c }}>
      {icon && <Icon name={icon} size={11} />}{children}
    </span>
  );
}

Object.assign(window, { contrastRatio, deltaE, panelBg, onAccent, deltaBand, themeVariantTokenIds, themesPinning, roleLabel, divergenceRow, Pill });
