// ADRO Sales Dashboard v2 — main app
// Borrowed Tweaks panel pattern from AOX WBS.
// Note: useState/useEffect/etc. already destructured in components.jsx (loaded first)

const STORAGE_TWEAKS = 'sales_tweaks_v1';

const DEFAULT_TWEAKS = {
  theme: 'dark',          // ADRO BX는 high-contrast 다크 기반
  density: 'comfortable',
  lang: 'ko',
  defaultView: 'overview',
  font: 'adro',           // Oxanium display + Outfit body
  accent: 'adro',         // ADRO 시그니처 그린
  fxBasis: 'monthly',     // 환율 기준 — 'monthly'(월평균 확정값) | 'daily'(일일환율)
};

function loadTweaks() {
  try { return { ...DEFAULT_TWEAKS, ...(JSON.parse(localStorage.getItem(STORAGE_TWEAKS)) || {}) }; }
  catch { return { ...DEFAULT_TWEAKS }; }
}
function saveTweaksLs(tw) { localStorage.setItem(STORAGE_TWEAKS, JSON.stringify(tw)); }

const ACCENTS = {
  // ADRO brand — signature vivid green + orange (from BX guide adro2.0_BX_1213.pdf)
  // "NOT FOR EVERYBODY" / carbon fiber / high-contrast aesthetic.
  adro:    { light:'#00C853', dark:'#00F100', soft:'rgba(0,241,0,0.10)',    softDark:'rgba(0,241,0,0.18)' },
  blue:    { light:'#007AFF', dark:'#0A84FF', soft:'rgba(0,122,255,0.12)',  softDark:'rgba(10,132,255,0.22)' },
  indigo:  { light:'#5856D6', dark:'#5E5CE6', soft:'rgba(88,86,214,0.14)',  softDark:'rgba(94,92,230,0.24)' },
  purple:  { light:'#AF52DE', dark:'#BF5AF2', soft:'rgba(175,82,222,0.14)', softDark:'rgba(191,90,242,0.24)' },
  pink:    { light:'#FF2D55', dark:'#FF375F', soft:'rgba(255,45,85,0.14)',  softDark:'rgba(255,55,95,0.24)' },
  orange:  { light:'#FF5000', dark:'#FF6A1A', soft:'rgba(255,80,0,0.10)',   softDark:'rgba(255,106,26,0.20)' },
  green:   { light:'#34C759', dark:'#30D158', soft:'rgba(52,199,89,0.14)',  softDark:'rgba(48,209,88,0.24)' },
  graphite:{ light:'#3A3A3C', dark:'#8E8E93', soft:'rgba(58,58,60,0.12)',   softDark:'rgba(142,142,147,0.2)' },
};

const FONT_STACKS = {
  // ADRO BX — Oxanium for headlines (display geometric sans), Outfit for body
  adro:       '"Outfit", "Pretendard", -apple-system, "Apple SD Gothic Neo", sans-serif',
  sf:         '-apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Pretendard", "Apple SD Gothic Neo", system-ui, sans-serif',
  inter:      '"Inter", -apple-system, "Pretendard", "Apple SD Gothic Neo", sans-serif',
  pretendard: '"Pretendard", -apple-system, "Apple SD Gothic Neo", sans-serif',
  serif:      '"New York", "Noto Serif KR", "Times New Roman", serif',
};

function applyTweaks(tw) {
  const root = document.documentElement;
  root.dataset.theme = tw.theme;
  root.dataset.density = tw.density;
  const acc = ACCENTS[tw.accent] || ACCENTS.blue;
  const isDark = tw.theme === 'dark';
  root.style.setProperty('--accent', isDark ? acc.dark : acc.light);
  root.style.setProperty('--accent-soft', isDark ? acc.softDark : acc.soft);
  root.style.setProperty('--font-sans', FONT_STACKS[tw.font] || FONT_STACKS.sf);
}

// --- i18n -------------------------------------------------------------
const TRANS = {
  ko: {
    appTitle: 'ADRO Sales',
    views: { overview: '개요', kpi: 'KPI 달성률', breakdown: '상세 분석', query: '질문', product: '제품 분석', regional: '지역 분석' },
    fxBasis: {
      label: '환율',
      monthly: '월평균',
      daily: '일일',
      tipMonthly: '한국은행 월평균 환율(확정값) 기준. 아직 발표 안 된 월은 일일환율로 대체.',
      tipDaily: '주문일자 기준 한국은행 일일환율. 당월 매출도 즉시 반영(잠정값).',
      notice: (months, n) => `월평균 환율이 아직 발표되지 않은 ${months} ${n.toLocaleString()}건은 일일환율로 대체해 표시 중입니다.`,
    },
    tweaks: {
      title: '환경설정',
      theme: '테마', density: '밀도', lang: '언어', defaultView: '기본 뷰', font: '폰트', accent: '강조색',
      comfortable: '편안', compact: '컴팩트', light: '☀︎ Light', dark: '☾ Dark',
    },
    admin: '관리자 설정',
    signOut: '로그아웃',
    loading: '불러오는 중…',
    empty: '데이터가 없습니다',
  },
  en: {
    appTitle: 'ADRO Sales',
    views: { overview: 'Overview', kpi: 'KPI Progress', breakdown: 'Breakdown', query: 'Ask', product: 'Product Analysis', regional: 'Regional' },
    fxBasis: {
      label: 'FX',
      monthly: 'Monthly avg',
      daily: 'Daily',
      tipMonthly: 'BOK monthly average rate (final). Months not yet published fall back to the daily rate.',
      tipDaily: 'BOK daily rate by order date. Reflects current-month revenue immediately (provisional).',
      notice: (months, n) => `${n.toLocaleString()} row(s) from ${months} use the daily FX rate — their monthly average rate is not published yet.`,
    },
    tweaks: {
      title: 'Preferences',
      theme: 'Theme', density: 'Density', lang: 'Language', defaultView: 'Default view', font: 'Font', accent: 'Accent',
      comfortable: 'Comfortable', compact: 'Compact', light: '☀︎ Light', dark: '☾ Dark',
    },
    admin: 'Admin Settings',
    signOut: 'Sign out',
    loading: 'Loading…',
    empty: 'No data',
  },
};

// --- Tweaks Panel -----------------------------------------------------
function TweaksPanel({ tweaks, setTweaks, onClose, lang, perms }) {
  const t = TRANS[lang];
  return (
    <div className="tweaks-panel" style={{
      position:'fixed', top: 56, right: 16, width: 320,
      background:'var(--bg-solid)', border:'1px solid var(--border)',
      borderRadius: 14, boxShadow:'var(--shadow-3)', padding: 16, zIndex: 90,
    }}>
      <header style={{display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom: 14}}>
        <h3 style={{fontSize: 15, fontWeight: 600, margin: 0}}>{t.tweaks.title}</h3>
        <button className="iconbtn" onClick={onClose} style={{
          background:'none', border:'none', fontSize: 14, cursor:'pointer', color:'var(--fg-muted)',
        }}>✕</button>
      </header>

      <TweakRow label={t.tweaks.theme}>
        {[['light', t.tweaks.light], ['dark', t.tweaks.dark]].map(([k, l]) => (
          <SegBtn key={k} active={tweaks.theme === k} onClick={() => setTweaks({ theme: k })}>{l}</SegBtn>
        ))}
      </TweakRow>

      <TweakRow label={t.tweaks.density}>
        {[['comfortable', t.tweaks.comfortable], ['compact', t.tweaks.compact]].map(([k, l]) => (
          <SegBtn key={k} active={tweaks.density === k} onClick={() => setTweaks({ density: k })}>{l}</SegBtn>
        ))}
      </TweakRow>

      <TweakRow label={t.tweaks.lang}>
        {[['ko', '한국어'], ['en', 'English']].map(([k, l]) => (
          <SegBtn key={k} active={tweaks.lang === k} onClick={() => setTweaks({ lang: k })}>{l}</SegBtn>
        ))}
      </TweakRow>

      <TweakRow label={t.tweaks.defaultView}>
        {['overview', 'kpi', 'breakdown', 'query', 'product', 'regional'].map(v => (
          <SegBtn key={v} active={tweaks.defaultView === v} onClick={() => setTweaks({ defaultView: v })}>
            {TRANS[lang].views[v]}
          </SegBtn>
        ))}
      </TweakRow>

      <TweakRow label={t.tweaks.font}>
        {[['adro', 'ADRO'], ['sf', 'SF'], ['inter', 'Inter'], ['pretendard', 'Pret'], ['serif', 'Serif']].map(([k, l]) => (
          <SegBtn key={k} active={tweaks.font === k} onClick={() => setTweaks({ font: k })}>{l}</SegBtn>
        ))}
      </TweakRow>

      <TweakRow label={t.tweaks.accent}>
        <div style={{display:'flex', gap: 6, flexWrap:'wrap'}}>
          {Object.entries(ACCENTS).map(([k, v]) => (
            <button key={k} onClick={() => setTweaks({ accent: k })}
              style={{
                width: 22, height: 22, borderRadius: 999,
                background: tweaks.theme === 'dark' ? v.dark : v.light,
                border: tweaks.accent === k ? '2px solid var(--fg)' : '1px solid var(--border)',
                cursor:'pointer',
              }}/>
          ))}
        </div>
      </TweakRow>

      {perms && (
        <div style={{marginTop: 10, paddingTop: 12, borderTop: '1px dashed var(--divider)'}}>
          <div style={{fontSize: 10.5, textTransform:'uppercase', letterSpacing: 0.06, color:'var(--fg-subtle)', marginBottom: 6}}>
            {lang === 'en' ? 'Current role' : '현재 권한'}
          </div>
          <SALES_AUTH.RoleBadge role={perms.role} team={perms.team} lang={lang}/>
        </div>
      )}
    </div>
  );
}

function TweakRow({ label, children }) {
  return (
    <div style={{marginBottom: 12}}>
      <label style={{display:'block', fontSize: 11, fontWeight: 600, color:'var(--fg-muted)', marginBottom: 6}}>{label}</label>
      <div style={{display:'flex', gap: 4, flexWrap:'wrap'}}>{children}</div>
    </div>
  );
}
function SegBtn({ active, onClick, children }) {
  return (
    <button onClick={onClick} style={{
      padding: '6px 10px', fontSize: 12, fontWeight: 500,
      background: active ? 'var(--accent-soft)' : 'var(--surface-2)',
      color: active ? 'var(--accent)' : 'var(--fg-2)',
      border: `1px solid ${active ? 'var(--accent)' : 'var(--border)'}`,
      borderRadius: 8, cursor:'pointer',
    }}>{children}</button>
  );
}

// --- FX 기준 토글 (월평균 / 일일 환율) --------------------------------
// 헤더 우측 세그먼트 버튼. 전환 시 모든 view의 KRW 금액이 해당 기준으로 재계산됨.
function FxBasisToggle({ value, onChange, lang }) {
  const tf = TRANS[lang].fxBasis;
  const opts = [['monthly', tf.monthly, tf.tipMonthly], ['daily', tf.daily, tf.tipDaily]];
  return (
    <div style={{display:'flex', alignItems:'center', gap: 6}}>
      <span style={{
        fontSize: 10, fontWeight: 700, letterSpacing: 0.05,
        textTransform:'uppercase', color:'var(--fg-subtle)',
      }}>{tf.label}</span>
      <div style={{display:'flex', border:'1px solid var(--border)', borderRadius: 8, overflow:'hidden'}}>
        {opts.map(([k, label, tip], i) => (
          <button key={k} onClick={() => onChange(k)} title={tip} style={{
            padding:'5px 11px', fontSize: 12, fontWeight: 600, cursor:'pointer',
            background: value === k ? 'var(--accent-soft)' : 'transparent',
            color: value === k ? 'var(--accent)' : 'var(--fg-2)',
            border:'none', borderLeft: i === 0 ? 'none' : '1px solid var(--border)',
            whiteSpace:'nowrap',
          }}>{label}</button>
        ))}
      </div>
    </div>
  );
}

// fallbackMonths(['2026-05', ...])를 사람이 읽는 문자열로. 하드코딩 없이
// 현재 연도면 "5월", 다른 연도면 "2025년 12월". 여러 달이면 ·로 연결.
function formatFxMonths(months, lang) {
  if (!months || !months.length) return '';
  const cy = new Date().getFullYear();
  const MS_EN = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
  const parts = months.map(ym => {
    const [y, m] = ym.split('-').map(Number);
    if (lang === 'en') return y === cy ? MS_EN[m - 1] : `${MS_EN[m - 1]} ${y}`;
    return y === cy ? `${m}월` : `${y}년 ${m}월`;
  });
  return parts.join(lang === 'en' ? ', ' : '·');
}

// 월평균 기준일 때 일부 행이 일일환율로 대체됐으면 안내 배너 표시.
// 일일 기준에서는 누락이 없으므로 배너 없음.
function FxBasisNotice({ lang, fxBasis, dataVersion }) {
  const [stats, setStats] = useState(null);
  useEffect(() => {
    let cancelled = false;
    SALES_DB.fetchAllSalesData()
      .then(() => { if (!cancelled) setStats(SALES_DB.getFxBasisStats()); })
      .catch(() => { /* 데이터 로드 실패 — 배너 생략 */ });
    return () => { cancelled = true; };
  }, [fxBasis, dataVersion]);
  if (fxBasis !== 'monthly' || !stats || !stats.fallbackRows) return null;
  const tf = TRANS[lang].fxBasis;
  const monthsStr = formatFxMonths(stats.fallbackMonths, lang);
  return (
    <div style={{
      display:'flex', alignItems:'center', gap: 8, marginBottom: 16,
      padding:'9px 13px', borderRadius: 10,
      background:'var(--accent-soft)', border:'1px solid var(--accent)',
      fontSize: 12.5, color:'var(--fg-2)', lineHeight: 1.45,
    }}>
      <span style={{color:'var(--accent)', display:'flex', flexShrink: 0}}>
        <Icon name="info" size={15}/>
      </span>
      <span>{tf.notice(monthsStr, stats.fallbackRows)}</span>
    </div>
  );
}

// --- App Shell --------------------------------------------------------
function App() {
  const [tweaks, setTweaksState] = useState(loadTweaks);
  const [view, setView] = useState(() => loadTweaks().defaultView || 'overview');
  const [showTweaks, setShowTweaks] = useState(false);
  const [showAdmin, setShowAdmin] = useState(false);
  // Bumped on manual data refresh — used as a key to remount views and re-fetch.
  const [dataVersion, setDataVersion] = useState(0);
  const [refreshing, setRefreshing] = useState(false);
  // Track which views have ever been opened, so we keep them mounted (display
  // toggle) instead of re-mounting on every tab switch. Cuts perceived
  // tab-switch latency to near-zero after the first visit.
  const [mountedViews, setMountedViews] = useState(() => new Set([loadTweaks().defaultView || 'overview']));
  useEffect(() => {
    setMountedViews(prev => prev.has(view) ? prev : new Set([...prev, view]));
  }, [view]);

  const [session, setSession] = useState(null);
  // roster는 표시용 메타데이터(이름/role 라벨). 게이트 아님.
  // null = 로스터에 없음 (정상 케이스 — 프로필 default로 동작)
  const [roster, setRoster] = useState(null);
  // dashAccess가 단일 권한 게이트. 3-state — undefined=체크 전(로딩), []=권한 없음, [...]=권한 있음
  const [dashAccess, setDashAccess] = useState(undefined);
  const [authLoading, setAuthLoading] = useState(true);

  const lang = tweaks.lang;
  const t = TRANS[lang];
  const perms = SALES_AUTH.permissionsFor(roster);

  useEffect(() => { applyTweaks(tweaks); saveTweaksLs(tweaks); }, [tweaks]);

  const setTweaks = useCallback(patch => {
    setTweaksState(prev => ({ ...prev, ...patch }));
  }, []);

  // 환율 기준 토글 — db 레이어에 반영하고 모든 view를 재계산(dataVersion bump).
  // 첫 실행은 localStorage 복원값을 db에 알려주기만 하고 remount는 생략.
  const fxFirstRun = useRef(true);
  useEffect(() => {
    SALES_DB.setFxBasis(tweaks.fxBasis);
    if (fxFirstRun.current) { fxFirstRun.current = false; return; }
    setDataVersion(v => v + 1);
  }, [tweaks.fxBasis]);

  // Auth bootstrap
  // 권한 게이트는 dashboard_access 단일 — 0007 RLS 통일에 맞춰 프론트도 동일.
  // roster는 표시용(이름/role 라벨)이므로 fetch 실패해도 게이트를 막지 않는다 (Promise.allSettled).
  // freeze 대응:
  //   - getSession / dashAccess는 5초 timeout — hang 시 reject로 unblock
  //   - visibilitychange 핸들러 — 30초+ hidden 후 visible 시 백그라운드 재bootstrap
  //   - 동시에 두 bootstrap이 돌지 않도록 bootstrapping 플래그
  useEffect(() => {
    let cancelled = false;
    let unsub = null;
    let hiddenAt = null;
    let bootstrapping = false;

    const AUTH_TIMEOUT_MS = 5000;
    const VIS_FREEZE_THRESHOLD_MS = 30 * 1000;

    function withTimeout(promise, label) {
      return Promise.race([
        promise,
        new Promise((_, reject) => setTimeout(
          () => reject(new Error(label + ' timeout after ' + AUTH_TIMEOUT_MS + 'ms')),
          AUTH_TIMEOUT_MS
        )),
      ]);
    }

    // session + dashAccess(게이트) + roster(메타) 를 다시 가져온다.
    // roster는 non-blocking — 실패/누락은 정상 케이스이므로 null로 폴백.
    async function fetchAuthBundle(s) {
      const [rosterRes, daRes] = await Promise.allSettled([
        SALES_DB.getRosterEntry(s.user.email),
        withTimeout(SALES_DB.getDashboardAccess(s.user.id), 'dashAccess'),
      ]);
      const roster = rosterRes.status === 'fulfilled' ? rosterRes.value : null;
      // dashAccess가 실패하면 [] — "권한 없음" 화면으로 빠짐(무한 로딩 X).
      const da = daRes.status === 'fulfilled' ? daRes.value : [];
      if (rosterRes.status === 'rejected') console.warn('[auth] roster fetch failed (non-blocking)', rosterRes.reason);
      if (daRes.status === 'rejected') console.warn('[auth] dashAccess fetch failed', daRes.reason);
      return { roster, da };
    }

    // bootstrap을 함수로 추출 — 첫 mount + visibility 복구 둘 다 사용.
    // 두 번째 호출부터는 setAuthLoading(true) 안 함 — 화면 깜빡임 없이 백그라운드 갱신.
    async function bootstrap() {
      if (cancelled || bootstrapping) return;
      bootstrapping = true;
      try {
        const s = await withTimeout(SALES_DB.getSession(), 'getSession');
        if (cancelled) return;
        setSession(s);
        if (s?.user) {
          const { roster, da } = await fetchAuthBundle(s);
          if (cancelled) return;
          setRoster(roster);
          setDashAccess(da);
        } else {
          setRoster(null);
          setDashAccess(null);
        }
      } catch (e) {
        // freeze hang을 timeout으로 푼 케이스 또는 실제 네트워크 에러.
        // 화면을 unblock해서 사용자가 ↻로 재시도할 수 있게 함.
        console.warn('[auth] bootstrap failed', e);
      } finally {
        bootstrapping = false;
        if (!cancelled) setAuthLoading(false);
      }
    }

    // 첫 bootstrap → 그 후 onAuthChange 등록
    (async () => {
      await bootstrap();
      if (cancelled) return;
      const { data } = await SALES_DB.onAuthChange(async (s) => {
        if (cancelled) return;
        setSession(s);
        if (s?.user) {
          // sign-in 직후 "접근권한 없음" 깜빡임 방지: dashAccess가 이미 set돼 있으면 그대로 두고
          // 새로 fetch가 끝나면 덮어쓴다 (token refresh 케이스). 처음 set이라면 undefined → 로딩 화면.
          setDashAccess(prev => prev == null ? undefined : prev);
          const { roster, da } = await fetchAuthBundle(s);
          if (cancelled) return;
          setRoster(roster);
          setDashAccess(da);
        } else {
          setRoster(null);
          setDashAccess(null);
        }
      });
      unsub = data?.subscription;
    })();

    // Visibility 복구 — 30초+ hidden 후 visible 시 백그라운드로 auth re-bootstrap.
    // 이미 session 있으면 setSession은 같은 세션으로 갱신만 (no-op 비슷),
    // 봉인된 상태에서 깨어났어도 fresh 호출은 정상 응답할 가능성 높음.
    const onVis = () => {
      if (document.visibilityState === 'hidden') {
        hiddenAt = Date.now();
      } else if (document.visibilityState === 'visible') {
        const wasHiddenMs = hiddenAt ? (Date.now() - hiddenAt) : 0;
        hiddenAt = null;
        if (wasHiddenMs >= VIS_FREEZE_THRESHOLD_MS) {
          console.info('[auth] tab visible after ' + wasHiddenMs + 'ms — refreshing auth');
          bootstrap();
        }
      }
    };
    document.addEventListener('visibilitychange', onVis);

    return () => {
      cancelled = true;
      document.removeEventListener('visibilitychange', onVis);
      if (unsub) unsub.unsubscribe();
    };
  }, []);

  async function handleSignOut() {
    setSession(null);
    setRoster(null);
    setDashAccess(null);
    try { await SALES_DB.signOut(); } catch (_) { /* ignore */ }
  }

  // Clear cached sales rows and force every view to re-fetch.
  async function handleDataRefresh() {
    if (refreshing) return;
    setRefreshing(true);
    try {
      SALES_DB.invalidateSalesCache();
      // Pre-warm so the next view render returns from cache instantly.
      await SALES_DB.fetchAllSalesData(true);
    } catch (e) {
      console.error('[refresh] failed', e);
    } finally {
      setRefreshing(false);
      setDataVersion(v => v + 1);
    }
  }

  if (authLoading) {
    return <div style={centerStyle}>{t.loading}</div>;
  }
  if (!session) {
    return <SALES_AUTH.LoginScreen lang={lang} onLangToggle={() => setTweaks({ lang: lang === 'ko' ? 'en' : 'ko' })}/>;
  }
  // dashAccess만 게이트. undefined = 아직 fetch 중 → 로딩.
  // (roster는 메타데이터이므로 게이트에서 제외 — DB의 0007 RLS와 일치)
  if (dashAccess === undefined) {
    return <div style={centerStyle}>{t.loading}</div>;
  }
  // dashAccess가 비어있거나 sales 권한이 없으면 거부.
  // 빈 배열도 거부에 해당 — 무한 로딩에 갇히지 않도록 명시 화면을 띄운다.
  if (!Array.isArray(dashAccess) || !SALES_DB.hasDashboardAccess(dashAccess, 'sales')) {
    return (
      <AccessDeniedScreen
        lang={lang} t={t}
        otherDashboards={Array.isArray(dashAccess) ? dashAccess.map(a => a.dashboard) : []}
        onSignOut={handleSignOut}
      />
    );
  }

  return (
    <div style={{minHeight:'100vh', background:'var(--bg)'}}>
      {/* Top bar */}
      <header style={{
        position:'sticky', top: 0, zIndex: 50,
        background:'var(--bg-elev)', backdropFilter:'saturate(180%) blur(20px)',
        borderBottom:'1px solid var(--border)',
        padding: '10px 20px',
        display:'flex', alignItems:'center', gap: 16,
      }}>
        <div style={{fontSize: 15, fontWeight: 700, letterSpacing: 0.1, color:'var(--fg)'}}>
          {t.appTitle}
        </div>
        <nav style={{display:'flex', gap: 4, marginLeft: 12}}>
          {['overview', 'kpi', 'breakdown', 'query', 'product', 'regional'].map(v => (
            <button key={v} onClick={() => setView(v)} style={{
              padding: '6px 12px', fontSize: 13, fontWeight: 500,
              background: view === v ? 'var(--accent-soft)' : 'transparent',
              color: view === v ? 'var(--accent)' : 'var(--fg-2)',
              border: 'none', borderRadius: 8, cursor:'pointer',
            }}>{t.views[v]}</button>
          ))}
        </nav>

        <div style={{flex: 1}}/>

        <FxBasisToggle value={tweaks.fxBasis} onChange={b => setTweaks({ fxBasis: b })} lang={lang}/>

        <button
          onClick={handleDataRefresh}
          className="iconbtn"
          title={lang === 'en' ? 'Refresh data' : '데이터 새로고침'}
          disabled={refreshing}
          style={{
            ...iconBtnStyle,
            opacity: refreshing ? 0.5 : 1,
            cursor: refreshing ? 'wait' : 'pointer',
          }}>
          <span style={{
            display:'inline-block',
            animation: refreshing ? 'aox-spin 0.9s linear infinite' : 'none',
            fontSize: 14,
          }}>↻</span>
        </button>
        {perms.isAdmin && (
          <button onClick={() => setShowAdmin(true)} className="iconbtn" title={t.admin} style={iconBtnStyle}>⚙</button>
        )}
        <button onClick={() => setShowTweaks(s => !s)} className="iconbtn" title={t.tweaks.title} style={iconBtnStyle}>◐</button>
        <SALES_AUTH.UserMenu session={session} roster={roster} perms={perms} lang={lang} onSignOut={handleSignOut}/>
      </header>

      {/* Main view area — visited views stay mounted (display toggle) so
          re-entry is instant. dataVersion in the key forces remount on manual refresh. */}
      <main style={{padding: '20px 24px', maxWidth: 1400, margin: '0 auto'}}>
        <FxBasisNotice lang={lang} fxBasis={tweaks.fxBasis} dataVersion={dataVersion}/>
        {mountedViews.has('overview') && (
          <div style={{ display: view === 'overview' ? 'block' : 'none' }}>
            <OverviewView key={'ov-'+dataVersion} lang={lang} perms={perms}/>
          </div>
        )}
        {mountedViews.has('kpi') && (
          <div style={{ display: view === 'kpi' ? 'block' : 'none' }}>
            <KpiView key={'kp-'+dataVersion} lang={lang} perms={perms}/>
          </div>
        )}
        {mountedViews.has('breakdown') && (
          <div style={{ display: view === 'breakdown' ? 'block' : 'none' }}>
            <BreakdownView key={'bd-'+dataVersion} lang={lang} perms={perms}/>
          </div>
        )}
        {mountedViews.has('query') && (
          <div style={{ display: view === 'query' ? 'block' : 'none' }}>
            <QueryView key={'qy-'+dataVersion} lang={lang} perms={perms}/>
          </div>
        )}
        {mountedViews.has('product') && (
          <div style={{ display: view === 'product' ? 'block' : 'none' }}>
            <ProductView key={'pd-'+dataVersion} lang={lang} perms={perms}/>
          </div>
        )}
        {mountedViews.has('regional') && (
          <div style={{ display: view === 'regional' ? 'block' : 'none' }}>
            <RegionalView key={'rg-'+dataVersion} lang={lang} perms={perms}/>
          </div>
        )}
      </main>

      {showTweaks && (
        <TweaksPanel tweaks={tweaks} setTweaks={setTweaks} onClose={() => setShowTweaks(false)}
          lang={lang} perms={perms}/>
      )}
      {showAdmin && (
        <AdminSettings onClose={() => setShowAdmin(false)} lang={lang} perms={perms}/>
      )}
    </div>
  );
}

const centerStyle = {
  minHeight:'100vh', display:'flex', alignItems:'center', justifyContent:'center',
  background:'var(--bg)', color:'var(--fg-muted)', fontSize: 14,
};

function AccessDeniedScreen({ lang, t, otherDashboards = [], onSignOut }) {
  return (
    <div style={centerStyle}>
      <div style={{textAlign:'center', maxWidth: 420, padding: 24}}>
        <div style={{fontSize: 15, fontWeight: 600, marginBottom: 8, color:'var(--fg)'}}>
          {lang === 'en' ? 'No access to Sales Dashboard' : 'Sales Dashboard 접근 권한이 없습니다'}
        </div>
        <div style={{fontSize: 13, color:'var(--fg-muted)', marginBottom: 16}}>
          {lang === 'en'
            ? 'Your account is not registered for the Sales Dashboard. Contact an admin to be added.'
            : '이 계정은 Sales Dashboard 접근 권한이 없습니다. 관리자에게 문의하세요.'}
        </div>
        {otherDashboards.length > 0 && (
          <div style={{fontSize: 12, color:'var(--fg-muted)', marginBottom: 16}}>
            {lang === 'en' ? 'You have access to:' : '접근 가능한 대시보드:'}
            {' '}{otherDashboards.join(', ')}
          </div>
        )}
        <button onClick={onSignOut} style={{
          padding:'8px 16px', background:'var(--surface-2)', border:'1px solid var(--border)',
          borderRadius: 8, cursor:'pointer', fontSize: 13, color:'var(--fg)',
        }}>{t.signOut}</button>
      </div>
    </div>
  );
}

const iconBtnStyle = {
  width: 32, height: 32, borderRadius: 8,
  background:'transparent', border:'1px solid var(--border)',
  color:'var(--fg-muted)', cursor:'pointer', fontSize: 16,
};

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