// app/main.jsx — 알파카 시제품 조립: 전체화면 챗봇(데스크톱) / 모바일 뷰(뷰포트 자동)
// 리본 ERP 목업 백드롭 제거. 업체(tenant) 선택은 챗봇 헤더로 이동. 데스크톱/모바일 수동 토글 삭제.

const { useState, useEffect } = React;
const RC = window.RibbonChat;
const { McpStatusBar, ChatThread, ChatInput, Icon, Spike, I } = RC;
const { HistorySidebar, FaqManager } = window.RibbonPanels;
const { AdminHub } = window.RibbonAdmin;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "#1ea8e7",
  "density": "regular",
  "showSources": true,
  "mcpStatus": true
}/*EDITMODE-END*/;

// 뷰포트 폭으로 모바일 판정(수동 토글 없음).
function useIsMobile(bp = 820) {
  const [m, setM] = useState(typeof window !== 'undefined' && window.innerWidth < bp);
  useEffect(() => {
    const on = () => setM(window.innerWidth < bp);
    window.addEventListener('resize', on);
    return () => window.removeEventListener('resize', on);
  }, [bp]);
  return m;
}

// ── 업체(tenant) 선택 — 챗봇 헤더용 ─────────────────────────
// 1단계-①: 일반 사용자에게는 업체 선택 UI 를 노출하지 않는다(소속 업체 고정 표시).
// selectable = /api/me 가 준 목록(다업체 권한자만 2곳 이상). 미인증(개발 모드)이면 전체.
function TenantSelect({ tenant, onTenantChange, selectable }) {
  const D = window.RibbonData;
  const T = window.RibbonI18n.useT();
  const list = (selectable && selectable.length ? selectable : D.TENANTS);
  const tn = tenant || list[0];
  if (list.length <= 1) {
    // 선택 불가 — 소속 업체를 정적으로 표시(권한 없는 업체 전환 시도 자체를 없앤다).
    return (
      <span title={T('tenant_title')}
        style={{ display: 'inline-flex', alignItems: 'center', height: 32, padding: '0 11px', borderRadius: 'var(--radius-md)',
          border: '1px solid var(--border-hairline)', background: 'var(--surface-soft)',
          fontFamily: 'var(--font-sans)', fontSize: 12.5, fontWeight: 600, color: 'var(--text-heading)', maxWidth: 220 }}>
        {tn ? tn.name + ' · ' + tn.region : ''}
      </span>
    );
  }
  return (
    <div style={{ position: 'relative', display: 'inline-flex', alignItems: 'center' }}>
      <select value={tn.id} onChange={e => onTenantChange && onTenantChange(e.target.value)} title={T('tenant_title')}
        style={{ appearance: 'none', WebkitAppearance: 'none', height: 40, padding: '0 28px 0 14px', borderRadius: 'var(--radius-md)',
          border: '1px solid var(--border-hairline)', background: 'var(--surface-card)', cursor: 'pointer',
          fontFamily: 'var(--font-sans)', fontSize: 12.5, fontWeight: 600, color: 'var(--text-heading)', maxWidth: 220 }}>
        {list.map(t => <option key={t.id} value={t.id}>{t.name + ' · ' + t.region}</option>)}
      </select>
      <span style={{ position: 'absolute', right: 9, pointerEvents: 'none', display: 'flex' }}>
        <Icon d="M6 9l6 6 6-6" size={14} color="var(--text-muted)" /></span>
    </div>
  );
}

// ── 언어(로케일) 선택 — 상단바용(한국어 기본 + 日本語/English/中文) ──
function LangSelect({ locale, onLocaleChange }) {
  const L = window.RibbonI18n;
  return (
    <div style={{ position: 'relative', display: 'inline-flex', alignItems: 'center' }}>
      <select value={locale} onChange={e => onLocaleChange && onLocaleChange(e.target.value)} title={L.t(locale, 'lang_title')}
        aria-label={L.t(locale, 'lang_title')}
        style={{ appearance: 'none', WebkitAppearance: 'none', height: 40, padding: '0 26px 0 14px', borderRadius: 'var(--radius-md)',
          border: '1px solid var(--border-hairline)', background: 'var(--surface-card)', cursor: 'pointer',
          fontFamily: 'var(--font-sans)', fontSize: 12.5, fontWeight: 600, color: 'var(--text-heading)' }}>
        {L.LOCALES.map(o => <option key={o.id} value={o.id}>{o.label}</option>)}
      </select>
      <span style={{ position: 'absolute', right: 8, pointerEvents: 'none', display: 'flex' }}>
        <Icon d="M6 9l6 6 6-6" size={14} color="var(--text-muted)" /></span>
    </div>
  );
}

// ── 채팅 헤더 (업체 선택 + 새 대화/FAQ/닫기) ─────────────────
function ChatHeader({ conv, tenant, onTenantChange, onNew, onFaq, onClose, mobile, selectable }) {
  const T = window.RibbonI18n.useT();
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 11, padding: mobile ? '10px 12px' : '11px 16px',
      borderBottom: '1px solid var(--border-hairline)', background: 'var(--surface-page)', flexShrink: 0 }}>
      <div style={{ width: 30, height: 30, borderRadius: 'var(--radius-full)', background: 'var(--surface-card)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
        <Spike size={17} /></div>
      <div style={{ flexShrink: 0 }}>
        <div style={{ fontFamily: 'var(--font-sans)', fontSize: 14.5, fontWeight: 600, color: 'var(--text-heading)', lineHeight: 1.15 }}>알파카</div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
          <span style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--color-success)' }} />
          <span style={{ fontSize: 10.5, color: 'var(--text-muted)', fontWeight: 500 }}>{conv.messages.length > 0 ? T('status_context') : T('status_online')}</span>
        </div>
      </div>
      <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 8 }}>
        {!mobile && <TenantSelect tenant={tenant} onTenantChange={onTenantChange} selectable={selectable} />}
        <div style={{ display: 'flex', alignItems: 'center', gap: 2 }}>
          <HBtn d={I.plus} label={T('hdr_new')} onClick={onNew} />
          <HBtn d="M8 2v4M16 2v4M3 9h18M5 5h14a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2z" label={T('hdr_faq')} onClick={onFaq} />
          <HBtn d={I.x} label={T('hdr_close')} onClick={onClose} />
        </div>
      </div>
    </div>
  );
}
function HBtn({ d, label, onClick }) {
  const [h, setH] = useState(false);
  return (
    <button onClick={onClick} title={label} onMouseEnter={() => setH(true)} onMouseLeave={() => setH(false)}
      style={{ width: 32, height: 32, borderRadius: 'var(--radius-md)', border: 'none', cursor: 'pointer',
        background: h ? 'var(--surface-card)' : 'transparent', display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'background .12s' }}>
      <Icon d={d} size={17} color="var(--text-muted)" /></button>
  );
}

// ── 채팅 컬럼 (헤더+상태+스레드+입력 / FAQ 오버레이) ─────────
function ChatColumn({ conv, t, tenant, onTenantChange, faqOpen, setFaqOpen, onClose, mobile, style, selectable }) {
  const opts = { showSources: t.showSources, mcpStatus: t.mcpStatus };
  const threadPad = t.density === 'compact' ? 'rb-pad-c' : t.density === 'comfy' ? 'rb-pad-x' : '';
  return (
    <div className={'rb-col rb-col-wide ' + (mobile ? 'rb-mobile ' : '') + threadPad}
      style={{ display: 'flex', flexDirection: 'column', minHeight: 0, position: 'relative', background: 'var(--surface-page)', ...style }}>
      <ChatHeader conv={conv} tenant={tenant} onTenantChange={onTenantChange} onNew={conv.reset} onFaq={() => setFaqOpen(true)} onClose={onClose} mobile={mobile} selectable={selectable} />
      {t.mcpStatus && <McpStatusBar status={conv.status} />}
      <ChatThread conv={conv} onChip={conv.send} opts={opts} />
      <ChatInput onSend={conv.send} busy={conv.busy} />
      {faqOpen && <FaqManager onClose={() => setFaqOpen(false)} onChip={conv.send} />}
      {conv.reportMsg && <window.RibbonFeedback.FeedbackModal conv={conv} tenant={tenant} onClose={conv.closeReport} />}
    </div>
  );
}

// ── 데스크톱: 사이드바(최근 대화) + 전체화면 챗봇 ───────────
function DesktopChat({ conv, t, tenant, onTenantChange, faqOpen, setFaqOpen, selectable }) {
  return (
    <div style={{ position: 'relative', flex: 1, minHeight: 0, display: 'flex', background: 'var(--surface-page)' }}>
      <HistorySidebar onNew={conv.reset} onChip={conv.send} onOpenFaq={() => setFaqOpen(true)}
        sessions={conv.sessions} activeId={conv.activeId} onSelect={conv.loadSession} onDelete={conv.deleteSession} />
      <ChatColumn conv={conv} t={t} tenant={tenant} onTenantChange={onTenantChange} faqOpen={faqOpen} setFaqOpen={setFaqOpen}
        onClose={conv.reset} style={{ flex: 1, minWidth: 0 }} selectable={selectable} />
    </div>
  );
}

// ── 모바일: 디바이스 프레임 안의 챗봇 ───────────────────────
// ⚠️ 여기서 IOSDevice(아이폰 베젤·가짜 상태바 9:41·홈 인디케이터) 프레임을 그리지 않는다.
// 그 프레임은 **데스크톱에서 모바일을 미리보기**하던 시절의 잔재다(파일 상단 "수동 토글 삭제" 참조).
// 지금은 뷰포트 폭으로 자동 판정하므로 이 컴포넌트가 렌더될 때는 **이미 진짜 좁은 화면**이고,
// 프레임을 덧그리면 "모바일 화면 안에 또 모바일 화면"이 되며 가로폭까지 잡아먹는다(2026-07-20 실측).
// 실기기에서는 뷰포트를 꽉 채우는 것이 맞다.
function MobileChat({ conv, t, tenant, faqOpen, setFaqOpen }) {
  return (
    <div style={{ flex: 1, minHeight: 0, display: 'flex', background: 'var(--surface-page)' }}>
      <ChatColumn conv={conv} t={t} tenant={tenant} onTenantChange={() => {}} faqOpen={faqOpen} setFaqOpen={setFaqOpen}
        mobile onClose={conv.reset} style={{ flex: 1, minWidth: 0, height: '100%', borderRadius: 0, border: 'none' }} />
    </div>
  );
}

// ── 임시 로그인 (테스트 전용) ────────────────────────────────
// 리본 ERP 가 알파카를 임베드하며 window.REBORN_AUTH_TOKEN 을 주입하는 것이 **정식 경로**다.
// 그 연동 전까지는 토큰이 없어 401 만 뜨므로, 개발자도구를 열지 않고 토큰을 넣을 자리를 준다.
//
// ⚠️ 토큰을 **브라우저에서 만들지 않는다** — 만들려면 HMAC 시크릿이 페이지에 실려야 하고, 그러면
//    누구나 임의 페이로드(타 업체·전사관리자)를 위조할 수 있다. 발급은 **서버**(POST /api/temp-login)가
//    하고 여기서는 받아 저장만 한다 → 클릭 한 번으로 바로 로그인된다(2026-07-20 붙여넣기 → 원클릭).
// ⚠️ 정식 오픈 시 서버가 allow_temp_login=false 를 내려주면 이 버튼은 사라진다(코드 수정 불필요).
function TempLoginButton({ allowed, needsCode }) {
  const [busy, setBusy] = useState(false);
  const A = window.RibbonAuth;
  if (!allowed || !A) return null;
  if (A.hasValid()) return null;   // 이미 로그인됨(만료 토큰은 무효로 봐서 다시 보인다)

  const onClick = async () => {
    if (busy) return;
    // 서버에 공유 암호가 설정돼 있으면(ready.temp_login_requires_code) 한 번만 입력받는다.
    // 검증은 **서버가** 한다 — 여기서는 전달만 하고, 맞는지 여부를 클라이언트가 판단하지 않는다.
    let code;
    if (needsCode) {
      const raw = window.prompt('임시 로그인 — 공유 암호를 입력하세요.');
      if (raw == null) return;          // 취소
      code = String(raw).trim();
      if (!code) return;
    }
    setBusy(true);
    try {
      const r = await fetch('/api/temp-login', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify(code ? { code } : {}),
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok || !j.token) { window.alert(j.error || `임시 로그인에 실패했습니다 (HTTP ${r.status}).`); return; }
      if (!A.set(j.token)) { window.alert('브라우저 저장소에 쓸 수 없습니다(시크릿 창인지 확인해 주세요).'); return; }
      location.reload();   // 토큰을 들고 전체 UI 재초기화 → 곧바로 로그인 상태
    } catch (e) {
      window.alert('임시 로그인 요청 실패: ' + ((e && e.message) || e));
    } finally {
      setBusy(false);
    }
  };

  return (
    <button onClick={onClick} disabled={busy}
      title={needsCode
        ? '리본 로그인 연동 전 테스트용 — 공유 암호를 입력하면 서버가 임시 토큰을 발급해 바로 로그인됩니다'
        : '리본 로그인 연동 전 테스트용 — 클릭하면 서버가 임시 토큰을 발급해 바로 로그인됩니다'}
      style={{ display: 'inline-flex', alignItems: 'center', gap: 6, padding: '6px 11px',
        borderRadius: 'var(--radius-sm)', cursor: busy ? 'progress' : 'pointer', fontFamily: 'var(--font-sans)',
        fontSize: 12.5, fontWeight: 600, background: 'var(--surface-soft)', whiteSpace: 'nowrap',
        border: '1px dashed var(--border-hairline)', color: 'var(--text-muted)', opacity: busy ? 0.6 : 1 }}>
      <Icon d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4M10 17l5-5-5-5M15 12H3" size={15} color="var(--text-muted)" />
      {busy ? '로그인 중…' : '임시 로그인'}
    </button>
  );
}

// ── 상단 데모 크롬 (언어 선택 + 챗봇 / 관리자 토글) ──────────
// ⚠️ 좁은 화면에서 라벨에 nowrap 이 없으면 **한글은 단어 중간에서도 줄바꿈**되어 "챗/봇",
// "관리/자", "시제/품" 처럼 쪼개지고 버튼 높이가 제각각이 되어 깨져 보인다(2026-07-20 실측).
// → 라벨은 전부 nowrap + flexShrink:0 으로 고정하고, 줄어들 여지는 **제목에만** 준다(말줄임).
//   모바일에서는 부가 정보인 '시제품' 배지를 숨겨 폭을 확보한다.
function TopChrome({ view, setView, locale, onLocaleChange, allowTempLogin, tempLoginNeedsCode, mobile }) {
  const T = window.RibbonI18n.useT();
  const NAV = [
    ['chat', T('nav_chat'), 'M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z'],
    ['admin', T('nav_admin'), 'M12 2l8 4v6c0 5-3.5 8-8 10-4.5-2-8-5-8-10V6z'],
  ];
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: mobile ? 8 : 14, padding: mobile ? '0 12px' : '0 18px',
      height: 52, flexShrink: 0, overflow: 'hidden',
      borderBottom: '1px solid var(--border-hairline)', background: 'var(--surface-page)', fontFamily: 'var(--font-sans)' }}>
      <span style={{ flexShrink: 0, display: 'inline-flex' }}><Spike size={18} /></span>
      {/* 유일하게 줄어드는 요소 — 공간이 모자라면 줄바꿈 대신 말줄임 */}
      <span style={{ fontSize: 13.5, fontWeight: 600, color: 'var(--text-heading)',
        whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0 }}>{T('app_title')}</span>
      {!mobile && <span style={{ fontSize: 11.5, color: 'var(--text-faint)', whiteSpace: 'nowrap', flexShrink: 0 }}>{T('prototype')}</span>}
      <div style={{ marginLeft: 'auto', display: 'inline-flex', alignItems: 'center', gap: mobile ? 6 : 10, flexShrink: 0 }}>
        <TempLoginButton allowed={allowTempLogin} needsCode={tempLoginNeedsCode} />
        <LangSelect locale={locale} onLocaleChange={onLocaleChange} />
        <div style={{ display: 'inline-flex', background: 'var(--surface-soft)', borderRadius: 'var(--radius-md)', padding: 3, gap: 2, border: '1px solid var(--border-hairline)', flexShrink: 0 }}>
          {NAV.map(([v, label, d]) => (
            <button key={v} onClick={() => setView(v)} style={{ display: 'inline-flex', alignItems: 'center', gap: 6,
              padding: mobile ? '6px 9px' : '6px 12px', borderRadius: 'var(--radius-sm)', border: 'none', cursor: 'pointer',
              fontFamily: 'var(--font-sans)', fontSize: 12.5, fontWeight: 600, transition: 'all .12s',
              whiteSpace: 'nowrap', flexShrink: 0,
              background: view === v ? 'var(--surface-page)' : 'transparent', boxShadow: view === v ? 'var(--shadow-soft)' : 'none',
              color: view === v ? 'var(--text-heading)' : 'var(--text-muted)' }}>
              <Icon d={d} size={15} color={view === v ? 'var(--accent)' : 'var(--text-muted)'} />{label}</button>
          ))}
        </div>
      </div>
    </div>
  );
}

// ── App ──────────────────────────────────────────────────────
// ── 위젯 임베드 모드(ALPACA-41) ──────────────────────────────────────────
// /embed?widget_key=… 로 열리면 iframe 안이다. 토큰은 URL 이 아니라 **호스트 로더의 postMessage** 로
// 받는다(URL 에 토큰을 실으면 리퍼러·히스토리에 남는다). 받은 토큰은 sessionStorage(탭 종료 시 소멸).
const EMBED = (() => {
  try {
    const u = new URL(window.location.href);
    if (u.pathname !== '/embed' && u.pathname !== '/embed/') return null;
    return { widgetKey: u.searchParams.get('widget_key') || '' };
  } catch (e) { return null; }
})();
if (EMBED) {
  try { const t0 = sessionStorage.getItem('alpaca.widget.token'); if (t0) window.REBORN_AUTH_TOKEN = t0; } catch (e) {}
  window.addEventListener('message', (ev) => {
    const d = ev.data;
    if (!d || d.source !== 'alpaca-widget-host') return;
    if (d.type === 'alpaca:token' && typeof d.token === 'string' && d.token) {
      window.REBORN_AUTH_TOKEN = d.token;
      try { sessionStorage.setItem('alpaca.widget.token', d.token); } catch (e) {}
      // 토큰 도착 → 리로드 없이 /api/me 재조회를 위해 커스텀 이벤트
      window.dispatchEvent(new CustomEvent('alpaca:token-set'));
    }
    if (d.type === 'alpaca:locale' && typeof d.locale === 'string') {
      window.dispatchEvent(new CustomEvent('alpaca:locale-set', { detail: d.locale }));
    }
  });
  // 로더에게 "준비됨" 신호 → 로더가 토큰을 보낸다(순서 경쟁 방지: 로더가 먼저 보내도 재전송하도록 설계).
  try { window.parent && window.parent !== window && window.parent.postMessage({ source: 'alpaca-widget', type: 'alpaca:ready' }, '*'); } catch (e) {}
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const conv = RC.useConversation();
  const isMobile = useIsMobile();
  const [view, setView] = useState('chat'); // 'chat' | 'admin'
  const [tenantId, setTenantId] = useState('thesafe');
  const [locale, setLocale] = useState(() => window.RibbonI18n.initialLocale());
  const [faqOpen, setFaqOpen] = useState(false);
  // 인증 컨텍스트(1단계-①) — /api/me: 로그인 사용자·소속 업체·**선택 가능 업체 목록**.
  // 일반 사용자는 자기 업체 1곳만 오므로 헤더의 업체 선택 UI 가 자동으로 사라진다.
  const [me, setMe] = useState(null);
  // 임시 로그인 버튼 노출 여부 — 서버(/api/ready)가 정한다. 정식 오픈 시 ALPACA_ALLOW_TEMP_LOGIN=0
  // 으로 끄면 버튼이 사라진다. /api/ready 는 **인증 없이** 호출 가능해야 한다(미로그인 상태에서
  // 버튼을 띄워야 하므로) — serve.ts 의 보호 경로 예외에 이미 포함돼 있다.
  const [allowTempLogin, setAllowTempLogin] = useState(false);
  // 공유 암호(ALPACA_TEMP_LOGIN_CODE) 설정 여부. true 면 발급 전에 암호를 입력받는다.
  // 값 자체는 서버가 절대 내려주지 않는다 — 여기서는 "물어봐야 하나"만 안다.
  const [tempLoginNeedsCode, setTempLoginNeedsCode] = useState(false);
  useEffect(() => {
    fetch('/api/ready').then(r => r.json()).then(j => {
      setAllowTempLogin(!!(j && j.allow_temp_login && j.auth)); // 인증이 꺼진 개발 모드면 버튼 불필요
      setTempLoginNeedsCode(!!(j && j.temp_login_requires_code));
    }).catch(() => {});
  }, []);
  const [meTick, setMeTick] = useState(0);
  useEffect(() => {
    if (!EMBED) return;
    const onTok = () => setMeTick((x) => x + 1);
    const onLoc = (e) => { if (e && e.detail) setLocale(e.detail); };
    window.addEventListener('alpaca:token-set', onTok);
    window.addEventListener('alpaca:locale-set', onLoc);
    return () => { window.removeEventListener('alpaca:token-set', onTok); window.removeEventListener('alpaca:locale-set', onLoc); };
  }, []);
  useEffect(() => {
    const f = (window.RibbonAuth ? window.RibbonAuth.fetch : fetch);
    f('/api/me').then(r => r.ok ? r.json() : null).then(j => {
      if (!j) return;
      setMe(j);
      if (j.tenant && j.tenant.id) setTenantId(j.tenant.id); // 소속 업체로 고정
    }).catch(() => {});
  }, [meTick]);
  const selectable = me && me.tenants ? me.tenants : null;
  const tenant = (selectable || window.RibbonData.TENANTS).find(x => x.id === tenantId)
    || window.RibbonData.TENANTS.find(x => x.id === tenantId);

  useEffect(() => { document.documentElement.style.setProperty('--accent', t.accent); }, [t.accent]);
  // 선택 업체를 전역에 노출 → chat.jsx 가 /api/chat 에 tenant_id 로 실어 보낸다(업체 스코프).
  useEffect(() => { window.RibbonData.currentTenantId = tenantId; }, [tenantId]);
  // 선택 언어를 전역·localStorage·<html lang> 에 반영 → chat.jsx 가 /api/chat 에 locale 로 실어 보낸다
  // (백엔드가 프롬프트 응답 언어를 맞춘다). 훅 밖 함수는 window.RibbonData.currentLocale 을 참조.
  useEffect(() => {
    window.RibbonData.currentLocale = locale;
    try { localStorage.setItem('ribbon.locale', locale); } catch (e) {}
    document.documentElement.lang = locale;
  }, [locale]);

  if (EMBED) {
    // 위젯: 상단 크롬(뷰 전환·임시로그인·언어)은 호스트가 담당 — 채팅 컬럼만 꽉 채운다.
    // 토큰이 아직 없으면 안내(호스트 세션 발급 대기) — 데이터 호출은 401 이라 어차피 안 나간다.
    const hasTok = !!(window.RibbonAuth && window.RibbonAuth.hasValid && window.RibbonAuth.hasValid());
    return (
      <window.RibbonI18n.LocaleCtx.Provider value={locale}>
      <div className="rb-embed" style={{ height: '100vh', display: 'flex', flexDirection: 'column', background: 'var(--surface-page)' }}>
        {!hasTok && meTick === 0 &&
          <div style={{ padding: '10px 14px', fontSize: 12.5, color: 'var(--text-muted)', borderBottom: '1px solid var(--border-hairline)', background: 'var(--surface-soft)' }}>
            연결 중… (호스트 시스템의 세션을 기다리고 있습니다)
          </div>}
        <MobileChat conv={conv} t={t} tenant={tenant} faqOpen={faqOpen} setFaqOpen={setFaqOpen} />
      </div>
      </window.RibbonI18n.LocaleCtx.Provider>
    );
  }
  return (
    <window.RibbonI18n.LocaleCtx.Provider value={locale}>
    <div style={{ height: '100vh', display: 'flex', flexDirection: 'column', background: 'var(--surface-soft)' }}>
      <TopChrome view={view} setView={setView} locale={locale} onLocaleChange={setLocale} allowTempLogin={allowTempLogin} tempLoginNeedsCode={tempLoginNeedsCode} mobile={isMobile} />
      {view === 'admin'
        ? <AdminHub />
        : isMobile
          ? <MobileChat conv={conv} t={t} tenant={tenant} faqOpen={faqOpen} setFaqOpen={setFaqOpen} />
          : <DesktopChat conv={conv} t={t} tenant={tenant} onTenantChange={setTenantId} faqOpen={faqOpen} setFaqOpen={setFaqOpen} selectable={selectable} />}

      <TweaksPanel>
        <TweakSection label="브랜드" />
        <TweakColor label="강조 색상" value={t.accent}
          options={['#1ea8e7', '#7c3aed', '#e83667', '#31414e']} onChange={v => setTweak('accent', v)} />
        <TweakSection label="채팅 영역" />
        <TweakRadio label="밀도" value={t.density} options={['compact', 'regular', 'comfy']} onChange={v => setTweak('density', v)} />
        <TweakSection label="기능" />
        <TweakToggle label="MCP 상태 표시줄" value={t.mcpStatus} onChange={v => setTweak('mcpStatus', v)} />
        <TweakToggle label="답변 출처 표시" value={t.showSources} onChange={v => setTweak('showSources', v)} />
      </TweaksPanel>
    </div>
    </window.RibbonI18n.LocaleCtx.Provider>
  );
}

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