/* app.jsx — shell: nav · routing · global theme/appearance · save handlers */
const { useState: useStateA, useEffect: useEffectA, useMemo: useMemoA } = React;

const CAT_OF_KIND = { color:'Color', spacing:'Spacing', type:'Typography', radius:'Radius', shadow:'Shadow' };

function NavItem({ icon, label, active, badge, onClick }) {
  return (
    <button onClick={onClick}
      className={cx('w-full flex items-center gap-3 h-9 px-2.5 rounded-[8px] text-[13.5px] font-medium transition-colors',
        active ? 'bg-accentWeak text-accent' : 'text-ink2 hover:text-ink hover:bg-hover')}>
      <Icon name={icon} size={17} stroke={active?1.8:1.6} />
      <span className="flex-1 text-left">{label}</span>
      {badge>0 && <CountBadge n={badge} tone={active?'accent':'muted'} />}
    </button>
  );
}

function App() {
  const [page, setPage] = useStateA('tokens');
  const [theme, setTheme] = useStateA(THEMES[0]);
  const [mode, setMode] = useStateA('light');
  const [changes, setChanges] = useStateA(CHANGES.slice());
  const [rev, setRev] = useStateA(0);
  const [drawer, setDrawer] = useStateA(null); // { token, isNew }
  const [compFocus, setCompFocus] = useStateA(null); // component id to focus from elsewhere
  const [lang, setLang] = useStateA('en');
  const [userMenu, setUserMenu] = useStateA(false);
  const [graphFocus, setGraphFocus] = useStateA(null);
  const [assetFocus, setAssetFocus] = useStateA(null);
  const [themeFocus, setThemeFocus] = useStateA(null);
  const [contentFocus, setContentFocus] = useStateA(null); // { tab, key } from a Changes jump
  const [rationaleMap, setRationaleMap] = useStateA({}); // theme-divergence reasons, keyed `${tokenId}@${mode}`
  const [themeDiffFocus, setThemeDiffFocus] = useStateA(null);
  const [role, setRole] = useStateA('manager');
  const [view, setView] = useStateA('site'); // 'site' (public) | 'admin' (console)
  setAppLang(lang); // keep i18n global in sync with state on every render
  setCurrentRole(role); // keep RBAC global in sync with state

  useEffectA(() => { document.documentElement.dataset.appearance = mode; }, [mode]);

  const ctx = useMemoA(() => ({ theme, mode }), [theme, mode]);

  /* ---- change-set upsert ---- */
  function upsertChange(entry) {
    setChanges(cs => {
      const i = cs.findIndex(c => c.target === entry.target && c.kind === entry.kind);
      if (i >= 0) { const n = cs.slice(); n[i] = { ...n[i], ...entry }; return n; }
      return [...cs, { id: 'c' + Date.now() + Math.round(Math.random()*99), impact:'minor', ...entry }];
    });
  }

  function buildSource(p) {
    if (p.valueMode === 'ref') return { type:'ref', ref:p.refTarget };
    if (p.kind === 'color') return { type:'value', value:p.val.color };
    if (p.kind === 'type') return { type:'value', value:p.val.size, lh:p.val.lh };
    if (p.kind === 'shadow') return { type:'value', value:p.val.shadow };
    return { type:'value', value:p.val.num };
  }
  function shortVal(kind, val, valueMode, refTarget) {
    if (valueMode==='ref') return '{'+refTarget+'}';
    if (kind==='color') return val.color;
    if (kind==='type') return val.size+' / '+val.lh+'px';
    if (kind==='shadow') return 'shadow';
    return val.num+'px';
  }

  function diffFor(oldTk, p, newSource) {
    const tmp = { ...TOKEN_MAP }; tmp[p.id] = { id:p.id, kind:p.kind, source:newSource };
    const saved = window.TOKEN_MAP; window.TOKEN_MAP = tmp;
    const newResolved = resolve(p.id, ctx); window.TOKEN_MAP = saved;
    const oldResolved = oldTk ? resolve(oldTk.id, ctx) : null;
    if (p.kind==='color') return { diffType:'color', from: oldResolved && oldResolved.value, to:newResolved.value };
    if (p.kind==='spacing'||p.kind==='radius') return { diffType:'num', from:((oldResolved&&oldResolved.value)||'—')+'px', to:newResolved.value+'px' };
    if (p.kind==='type') return { diffType:'num', from:((oldResolved&&oldResolved.value)||'—')+'px', to:newResolved.value+'px' };
    return { diffType:'refChange', from: oldTk?'changed':'', to: shortVal(p.kind,p.val,p.valueMode,p.refTarget) };
  }

  function onSaveToken(p) {
    const newSource = buildSource(p);
    if (p.isNew) {
      const layer = p.valueMode==='ref' ? 'semantic' : 'primitive';
      const tk = { id:p.id, layer, category:CAT_OF_KIND[p.kind], kind:p.kind, source:newSource,
        description:p.description, tags:p.tags, status:'new' };
      TOKENS.push(tk); TOKEN_MAP[p.id] = tk;
      upsertChange({ kind:'token', action:'added', target:p.id, scope:'all', diffType:'new',
        to: shortVal(p.kind,p.val,p.valueMode,p.refTarget) });
    } else if (p.scope === 'theme') {
      /* per-brand PIN: write the edited source into the active theme's overrides (sparse), leaving
         the base token untouched. Store a non-interfering `_basis` hash of the base source so we
         can later flag the pin as stale if the base drifts. */
      const tk = TOKEN_MAP[p.id];
      const th = THEMES.find(t => t.id === p.themeId) || theme;
      if (tk && th) {
        if (!th.overrides) th.overrides = {};
        const before = th.overrides[p.id] || null;
        th.overrides[p.id] = { ...newSource, _basis: sourceHash(tk.source) };
        // diff the resolved value under THIS theme (override vs. what it was)
        const oldResolved = before
          ? resolveWithOverride(p.id, th, before)
          : resolve(p.id, { theme: th, mode });
        const newResolved = resolveWithOverride(p.id, th, th.overrides[p.id]);
        const d = colorishDiff(p, oldResolved, newResolved);
        upsertChange({ kind:'token', action:'modified', target:p.id, scope: th.name, themeId: th.id, ...d });
      }
    } else {
      const tk = TOKEN_MAP[p.id];
      const before = { ...tk, source:{ ...tk.source } };
      tk.source = newSource; tk.description = p.description; tk.tags = p.tags;
      if (tk.status === 'published') tk.status = 'modified';
      const d = diffFor(before, p, newSource);
      // a base edit applies to every theme except those that pin this token (conceptually
      // "all except pinners"); the simple stamp is 'all'.
      upsertChange({ kind:'token', action:'modified', target:p.id, scope:'all', ...d });
    }
    setRev(r => r + 1);
  }

  /* resolve a token under a theme as if `ov` were its only-relevant override (isolated). */
  function resolveWithOverride(id, th, ov) {
    const tmpTheme = { ...th, overrides: { ...(th.overrides||{}), [id]: ov } };
    return resolve(id, { theme: tmpTheme, mode });
  }
  /* shape a Changes diff entry from two resolved results, by kind. */
  function colorishDiff(p, oldResolved, newResolved) {
    if (p.kind==='color') return { diffType:'color', from: oldResolved && oldResolved.value, to: newResolved.value };
    if (p.kind==='spacing'||p.kind==='radius'||p.kind==='type') return { diffType:'num', from:((oldResolved&&oldResolved.value)||'—')+'px', to:newResolved.value+'px' };
    return { diffType:'refChange', from: oldResolved?'changed':'', to: shortVal(p.kind,p.val,p.valueMode,p.refTarget) };
  }

  function onDeprecateToken(id) {
    const tk = TOKEN_MAP[id]; if (tk) tk.status = 'deprecated';
    upsertChange({ kind:'token', action:'deprecated', target:id, impact:'major', diffType:'deprecate',
      from: tk && tk.source.type==='ref' ? '{'+tk.source.ref+'}' : 'value' });
    setRev(r => r + 1);
  }

  /* ---- import tokens (Pixso / DTCG JSON) → merge into the set ---- */
  function onImportTokens(parsed) {
    let added = 0, updated = 0;
    parsed.forEach(tk => {
      const ex = TOKEN_MAP[tk.id];
      if (ex) {
        ex.source = tk.source; ex.description = tk.description;
        ex.kind = tk.kind; ex.category = tk.category; ex.layer = tk.layer;
        if (tk.tags && tk.tags.length) ex.tags = tk.tags;
        if (ex.status === 'published') ex.status = 'modified';
        updated++;
      } else {
        const ntk = { ...tk, status: tk.status || 'new' };
        TOKENS.push(ntk); TOKEN_MAP[tk.id] = ntk;
        added++;
      }
    });
    setRev(r => r + 1);
    return { added, updated };
  }

  /* ---- component article save → change-set ---- */
  function onSaveComponent(id, draft, change) {
    const c = COMPONENT_MAP[id];
    if (c) { Object.assign(c, draft); c.hasDraft = true; }
    upsertChange({ kind:'component', impact:'minor', ...change });
    setRev(r => r + 1);
  }

  /* ---- content (UX-writing) save → change-set ---- */
  function onSaveContent(change) {
    upsertChange({ kind:'content', impact:'patch', ...change });
    setRev(r => r + 1);
  }

  /* ---- theme-divergence rationale → side map + change-set (only if it actually diverges) ---- */
  function onSaveRationale(tokenId, mode, draft) {
    const key = tokenId + '@' + mode;
    setRationaleMap(m => { const prev = m[key];
      return { ...m, [key]: { tokenId, mode, baselineTheme:draft.baselineTheme, reason:draft.reason, status:draft.status, maxDE:draft.maxDE,
        author: prev ? prev.author : SESSION.user.handle, lastEditedBy: SESSION.user.name } }; });
    if ((draft.maxDE || 0) >= 1) {
      upsertChange({ kind:'rationale', action:'modified', impact:'patch', target:key, diffType:'theme-divergence',
        tokenId, mode, baselineTheme:draft.baselineTheme, reason:draft.reason, status:draft.status, maxDE:draft.maxDE });
    }
    setRev(r => r + 1);
  }

  /* ---- theme CRUD → change-set ---- */
  function onSaveTheme(d, isNew) {
    if (isNew) {
      const id = (d.id || d.name).toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'') || ('theme'+(THEMES.length+1));
      THEMES.push({ id, name:d.name, line:d.line, logo:d.logo||null, accent:{...d.accent} });
      upsertChange({ kind:'theme', action:'added', target:d.name, impact:'minor', diffType:'themeNew', swatch:d.accent[500] });
    } else {
      const th = THEMES.find(t=>t.id===d.id);
      if (th) { th.name=d.name; th.line=d.line; th.logo=d.logo||null; th.accent={...d.accent}; }
      upsertChange({ kind:'theme', action:'modified', target:d.name, impact:'minor', diffType:'themeEdit', swatch:d.accent[500] });
    }
    setRev(r => r + 1);
  }
  function onDeleteTheme(id) {
    const i = THEMES.findIndex(t=>t.id===id);
    if (i>=0) {
      const removed = THEMES[i];
      THEMES.splice(i,1);
      upsertChange({ kind:'theme', action:'removed', target:removed.name, impact:'major', diffType:'themeRemove' });
      if (theme.id===id && THEMES[0]) setTheme(THEMES[0]);
    }
    setRev(r => r + 1);
  }

  /* ---- unpin: remove a per-brand token override (theme falls back to inheriting the base) ---- */
  function onUnpinToken(themeId, tokenId) {
    const th = THEMES.find(t=>t.id===themeId);
    if (th && th.overrides && th.overrides[tokenId]) {
      delete th.overrides[tokenId];
      upsertChange({ kind:'token', action:'modified', target:tokenId, scope: th.name, themeId: th.id,
        diffType:'refChange', from:'pinned', to:tr('inherits base','继承基座') });
    }
    setRev(r => r + 1);
  }

  /* ---- Changes are immutable history → clicking a row jumps to its edit point ---- */
  function jumpToChange(c) {
    if (c.kind === 'token') {
      const tk = TOKEN_MAP[c.target];
      if (tk) setDrawer({ token: tk, isNew: false });
      setPage('tokens');
    } else if (c.kind === 'component') {
      const comp = COMPONENTS.find(x => x.name === c.target);
      if (comp) setCompFocus(comp.id);
      setPage('components');
    } else if (c.kind === 'asset') {
      setAssetFocus(c.target);
      setPage('assets');
    } else if (c.kind === 'theme') {
      const th = THEMES.find(x => x.name === c.target);
      if (th) setThemeFocus(th.id);
      setPage('themes');
    } else if (c.kind === 'content') {
      const t = String(c.target||''); const i = t.indexOf(':');
      const prefix = i>0 ? t.slice(0,i) : 'copy', key = i>0 ? t.slice(i+1) : t;
      setContentFocus({ tab: prefix==='guide'?'guides' : prefix==='term'?'glossary' : 'copy', key });
      setPage('content');
    } else if (c.kind === 'rationale') {
      setThemeDiffFocus({ tokenId: c.tokenId, mode: c.mode });
      setPage('themediff');
    }
  }

  const dirtyTokens = useMemoA(() => TOKENS.filter(t => ['new','modified','renamed','deprecated'].includes(t.status)).length, [rev]);

  if (view === 'site') return <SiteShell ctx={ctx} role={role} lang={lang} onSetLang={setLang} onEnterAdmin={() => setView('admin')} />;

  return (
    <div className="h-full flex bg-app text-ink">
      {/* ---- left nav ---- */}
      <nav className="w-[216px] shrink-0 flex flex-col bg-panel border-r border-line">
        <button onClick={()=>setView('site')} title={tr('Back to site','返回官网')}
          className="px-4 h-[60px] flex items-center gap-2.5 border-b border-line w-full text-left hover:bg-hover transition">
          <span className="w-7 h-7 rounded-[8px] flex items-center justify-center text-onAccent shrink-0" style={{ background:'var(--accent)' }}>
            <Icon name="cube" size={17} stroke={1.8} />
          </span>
          <div className="leading-tight min-w-0">
            <div className="text-[14px] font-semibold text-ink truncate">{SESSION.product}</div>
            <div className="text-[11px] text-ink3">{tr('Design System','设计系统')}</div>
          </div>
        </button>

        {/* theme + appearance, right under the title */}
        <div className="px-2.5 py-2.5 border-b border-line flex flex-col gap-2">
          <ThemeMenu theme={theme} setTheme={setTheme} full onManage={()=>setPage('themes')} />
          <div className="flex items-center gap-1.5 px-0.5">
            <span className="text-[11px] text-ink3 flex-1">{tr('Previewing','预览')} · {mode==='light'?tr('Light','浅色'):tr('Dark','深色')}</span>
            <Segmented size="sm" value={mode} onChange={setMode} options={[{value:'light',icon:'sun',label:''},{value:'dark',icon:'moon',label:''}]} />
          </div>
        </div>

        <div className="flex-1 p-2.5 flex flex-col gap-0.5">
          {/* work group — primary, day-to-day editing surfaces */}
          <NavItem icon="layers" label={tr('Tokens','Token')} active={page==='tokens'} onClick={()=>setPage('tokens')} />
          <NavItem icon="network" label={tr('Relationships','关系图谱')} active={page==='graph'} onClick={()=>{setGraphFocus(null);setPage('graph');}} />
          <NavItem icon="component" label={tr('Components','组件')} active={page==='components'} onClick={()=>setPage('components')} />
          <NavItem icon="grid"   label={tr('Asset library','资源库')} active={page==='assets'} onClick={()=>setPage('assets')} />
          <NavItem icon="edit"   label={tr('Content','文案')} active={page==='content'} onClick={()=>{setContentFocus(null);setPage('content');}} />

          {/* management group — lower priority, calm separator mirrors the bottom Changes group */}
          <div className="mt-3 pt-3 border-t border-line flex flex-col gap-0.5">
            <div className="px-2.5 pb-1 text-[10px] uppercase tracking-wide text-ink3">{tr('Manage','管理')}</div>
            <NavItem icon="gauge"  label={tr('Dashboard','看板')} active={page==='dashboard'} onClick={()=>setPage('dashboard')} />
            <NavItem icon="chat"   label={tr('Requirements','需求反馈')} active={page==='requirements'} onClick={()=>setPage('requirements')} />
            <NavItem icon="user"   label={tr('Members','成员')} active={page==='members'} onClick={()=>setPage('members')} />
            <NavItem icon="link"   label={tr('AI integration','AI 接入')} active={page==='ai'} onClick={()=>setPage('ai')} />
          </div>
        </div>

        {/* bottom: working set (changes + branch) + user — grouped because they're the same info */}
        <div className="p-2.5 border-t border-line flex flex-col gap-2">
          <NavItem icon="diff" label={tr('Changes','变更')} badge={changes.length} active={page==='changes'} onClick={()=>setPage('changes')} />
          <div className="flex items-center gap-2 px-2 h-8 rounded-[8px] bg-sunken border border-line">
            <Icon name="branch" size={13} className="text-ink2 shrink-0" />
            <span className="font-mono text-[11px] text-ink2 truncate flex-1">{SESSION.branch}</span>
            {dirtyTokens>0 && <span className="w-1.5 h-1.5 rounded-full bg-accent shrink-0" title={dirtyTokens+' dirty tokens'} />}
          </div>
          <div className="relative">
            {userMenu && (
              <>
                <div className="fixed inset-0 z-10" onClick={()=>setUserMenu(false)} />
                <div className="absolute bottom-0 left-[calc(100%+10px)] w-[256px] z-20 bg-panel border border-line rounded-[12px] p-2.5 shadow-[0_8px_28px_rgba(0,0,0,0.18)]" style={{ animation:'fadeIn 120ms ease' }}>
                  <div className="px-1 pb-1.5 text-[10.5px] uppercase tracking-wide text-ink3">{tr('Language','语言')}</div>
                  <Segmented size="sm" value={lang} onChange={(v)=>{ setLang(v); setAppLang(v); }} options={[{value:'en',label:'EN'},{value:'zh',label:'中文'}]} />
                  <div className="px-1 pb-1.5 pt-2.5 text-[10.5px] uppercase tracking-wide text-ink3">{tr('Acting role','当前角色')}</div>
                  <Segmented size="sm" value={role} onChange={(v)=>{ setRole(v); setCurrentRole(v); }} options={[{value:'manager',label:tr('Manager','管理员')},{value:'editor',label:tr('Editor','维护')},{value:'viewer',label:tr('Viewer','访客')}]} />
                </div>
              </>
            )}
            <button onClick={()=>setUserMenu(o=>!o)}
              className="w-full flex items-center gap-2 px-1.5 py-1.5 rounded-[8px] hover:bg-hover transition focus-ring">
              <span className="w-7 h-7 rounded-full flex items-center justify-center text-[11px] font-semibold text-onAccent shrink-0" style={{ background:theme.accent[500] }}>
                {SESSION.user.name.split(' ').map(s=>s[0]).join('')}
              </span>
              <div className="leading-tight min-w-0 flex-1 text-left">
                <div className="text-[12.5px] font-medium text-ink truncate">{SESSION.user.name}</div>
                <div className="text-[11px] text-ink3 truncate">{tr(ROLES[role].label, ROLES[role].labelZh)}</div>
              </div>
              <Icon name="more" size={15} className="text-ink3" />
            </button>
          </div>
        </div>
      </nav>

      {/* ---- content ---- */}
      <main className="flex-1 min-w-0 h-full overflow-hidden">
        {page==='dashboard' && <DashboardPage ctx={ctx} onNavigate={setPage} />}
        {page==='themediff' && <ThemeDiffPage ctx={ctx} theme={theme} rev={rev} rationaleMap={rationaleMap} onSaveRationale={onSaveRationale} focus={themeDiffFocus} clearFocus={()=>setThemeDiffFocus(null)} onBack={()=>setPage('themes')} />}
        {page==='tokens' && (
          <TokensPage ctx={ctx} mode={mode} setMode={setMode} theme={theme} setTheme={setTheme}
            selectedId={drawer && !drawer.isNew && drawer.token ? drawer.token.id : null}
            onOpenToken={(t)=>setDrawer({ token:t, isNew:false })}
            onNewToken={()=>setDrawer({ token:null, isNew:true })} onImport={onImportTokens} rev={rev} />
        )}
        {page==='assets' && <AssetsPage ctx={ctx} rev={rev} focusId={assetFocus} clearFocus={()=>setAssetFocus(null)} onSaveContent={onSaveContent} />}
        {page==='components' && <ComponentsPage ctx={ctx} focusId={compFocus} clearFocus={()=>setCompFocus(null)}
          onSaveComponent={onSaveComponent} onViewInGraph={(id)=>{ setGraphFocus('cmp.'+id); setPage('graph'); }} />}
        {page==='themes' && <ThemesPage theme={theme} rev={rev} onSaveTheme={onSaveTheme} onDeleteTheme={onDeleteTheme} onUnpin={onUnpinToken} focusId={themeFocus} clearFocus={()=>setThemeFocus(null)} onCompare={()=>setPage('themediff')} />}
        {page==='graph' && <GraphPage ctx={ctx} theme={theme} setTheme={setTheme} mode={mode}
          onOpenComponent={(id)=>{ setCompFocus(id); setGraphFocus(null); setPage('components'); }}
          initialFocus={graphFocus} clearInitialFocus={()=>setGraphFocus(null)} />}
        {page==='changes' && <ChangesPage changes={changes} setChanges={setChanges} onJump={jumpToChange} />}
        {page==='members' && <MembersPage />}
        {page==='requirements' && <RequirementsPage />}
        {page==='content' && <ContentPage ctx={ctx} onSaveContent={onSaveContent} focusKey={contentFocus} clearFocus={()=>setContentFocus(null)} rev={rev} />}
        {page==='ai' && <AIPlatformPage ctx={ctx} />}
      </main>

      {drawer && (
        <TokenDrawer token={drawer.token} isNew={drawer.isNew} ctx={ctx} theme={theme}
          onClose={()=>setDrawer(null)} onSave={onSaveToken} onDeprecate={onDeprecateToken} />
      )}
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
