// app.jsx — App shell, navigation, theme + Tweaks

const { useState: useAppState, useEffect: useAppEffect } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "dark",
  "density": "regular",
  "accent": "#5cc3e6",
  "showPaperBanner": true,
  "chartStyle": "area",
  "navStyle": "auto"
}/*EDITMODE-END*/;

const NAV_ITEMS = [
  { id: 'home',       label: '홈',         shortcut: 'G H', icon: (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M3 12l9-9 9 9M5 10v10h4v-6h6v6h4V10"/></svg>
  ) },
  { id: 'cycles',     label: '사이클',     icon: (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M21 12a9 9 0 1 1-3-6.7M21 4v5h-5"/></svg>
  ) },
  { id: 'portfolio',  label: '포트폴리오', icon: (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M3 3v18h18M7 14l4-4 4 4 5-5"/></svg>
  ) },
  { id: 'orders',     label: '주문', icon: (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M8 9h8M8 13h8M8 17h5"/></svg>
  ) },
  { id: 'problems',   label: '문제 로그', icon: (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M12 9v4M12 17h.01M10.29 3.86l-8.18 14.18A2 2 0 0 0 3.81 21h16.38a2 2 0 0 0 1.7-2.96L13.71 3.86a2 2 0 0 0-3.42 0z"/></svg>
  ), dot: true },
];

const NAV_ITEMS_2 = [
  { id: 'schedule',   label: '스케줄', icon: (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="4" width="18" height="17" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>
  ) },
  { id: 'news',       label: '뉴스', icon: (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="5" width="14" height="14" rx="1"/><path d="M17 9h4v8a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V5M7 9h6M7 13h6M7 17h3"/></svg>
  ) },
  { id: 'memory',     label: '메모리', icon: (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M12 2a4 4 0 0 0-4 4v1.5A3.5 3.5 0 0 0 4 11v2a3.5 3.5 0 0 0 4 3.46V18a4 4 0 0 0 8 0v-1.54A3.5 3.5 0 0 0 20 13v-2a3.5 3.5 0 0 0-4-3.5V6a4 4 0 0 0-4-4z"/></svg>
  ) },
  { id: 'backtest',   label: '백테스트', icon: (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M3 3v18h18M7 17l4-7 3 4 5-9"/></svg>
  ) },
  { id: 'system',     label: '시스템', icon: (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .35 1.85l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.7 1.7 0 0 0-1.85-.35 1.7 1.7 0 0 0-1 1.55V21a2 2 0 0 1-4 0v-.09a1.7 1.7 0 0 0-1.11-1.55 1.7 1.7 0 0 0-1.85.35l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.7 1.7 0 0 0 4.6 15a1.7 1.7 0 0 0-1.55-1H3a2 2 0 0 1 0-4h.09A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.35-1.85l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.7 1.7 0 0 0 9 4.6a1.7 1.7 0 0 0 1-1.55V3a2 2 0 0 1 4 0v.09A1.7 1.7 0 0 0 15 4.6a1.7 1.7 0 0 0 1.85-.35l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.7 1.7 0 0 0 19.4 9c.13.35.41.62.76.76h.04A2 2 0 0 1 21 14h-.09a1.7 1.7 0 0 0-1.55 1z"/></svg>
  ) },
];

// Mobile bottom nav: 5 tabs
const MOBILE_NAV = [
  NAV_ITEMS[0],
  NAV_ITEMS[1],
  NAV_ITEMS[2],
  NAV_ITEMS[3],
  { id: 'more', label: '더보기', icon: (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><circle cx="5" cy="12" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="19" cy="12" r="1.5"/></svg>
  ) },
];

function CodexUsageMini({ usage, loading }) {
  if (loading && !usage) {
    return (
      <div className="codex-mini">
        <div className="codex-mini-title"><span>Codex 사용량</span></div>
        <div className="codex-mini-empty">…</div>
      </div>
    );
  }
  if (!usage || usage.ok === false) {
    return (
      <div className="codex-mini">
        <div className="codex-mini-title"><span>Codex 사용량</span></div>
        <div className="codex-mini-empty">기록 없음</div>
      </div>
    );
  }
  const rows = [
    { key: '5h',   label: '5h', win: usage.primary },
    { key: 'wk',   label: '주', win: usage.secondary },
  ];
  return (
    <div className="codex-mini">
      <div className="codex-mini-title">
        <span>Codex 사용량</span>
        <span title={usage.plan_type || ''}>{usage.plan_type || '—'}</span>
      </div>
      {rows.map(r => {
        const used = r.win && typeof r.win.used_percent === 'number' ? r.win.used_percent : null;
        const tone = used == null ? '' : used >= 90 ? 'danger' : used >= 70 ? 'warn' : '';
        const width = used == null ? 0 : Math.max(0, Math.min(100, used));
        return (
          <div key={r.key} className="codex-mini-row">
            <div className="codex-mini-row-label">
              <span>{r.label}</span>
              <span className={cx('pct', tone)}>{used == null ? '—' : used.toFixed(1) + '%'}</span>
            </div>
            <div className="codex-mini-bar"><span className={tone} style={{ width: width + '%' }} /></div>
          </div>
        );
      })}
    </div>
  );
}

function Sidenav({ active, onNavigate }) {
  const sys = useApi(() => API.system.state(), [], { refreshMs: 30000 });
  const codex = useApi(() => API.system.codexUsage(), [], { refreshMs: 60000 });
  const sd = (sys.data) || {};
  const dbSize = sd.db && typeof sd.db.size_bytes === 'number' ? fmtBytes(sd.db.size_bytes) : '—';
  const ver = sd.version || '—';
  const paper = sd.paper_trading;

  return (
    <aside className="sidenav">
      <div className="sidenav-brand">
        <div className="sidenav-brand-mark">L</div>
        <div className="sidenav-brand-text">
          <b>LSTL</b>
          <span>stock-trading-lite</span>
        </div>
      </div>
      <div className="sidenav-section">MVP</div>
      {NAV_ITEMS.map(it => (
        <button key={it.id} className={cx('navitem', active === it.id && 'active')} onClick={() => onNavigate(it.id)}>
          <span className="navitem-icon">{it.icon}</span>
          <span>{it.label}</span>
          {it.badge && <span className="navitem-badge">{it.badge}</span>}
          {it.dot && <span className="navitem-badge dot" />}
        </button>
      ))}
      <div className="sidenav-section">확장</div>
      {NAV_ITEMS_2.map(it => (
        <button key={it.id} className={cx('navitem', active === it.id && 'active')} onClick={() => onNavigate(it.id)}>
          <span className="navitem-icon">{it.icon}</span>
          <span>{it.label}</span>
        </button>
      ))}
      <div className="sidenav-foot">
        <div className="row"><span>build</span><span>{ver}</span></div>
        <div className="row"><span>db</span><span>{dbSize}</span></div>
        <div className="row" style={paper === false ? { color: 'var(--critical)' } : { color: 'var(--warn)' }}>
          <span>mode</span><span>{paper === false ? 'LIVE' : paper === true ? 'PAPER' : '—'}</span>
        </div>
        <CodexUsageMini usage={codex.data} loading={codex.loading} />
      </div>
    </aside>
  );
}

function BottomNav({ active, onNavigate }) {
  return (
    <nav className="bottomnav">
      {MOBILE_NAV.map(it => (
        <button key={it.id} className={cx('bottomnav-item', active === it.id && 'active')} onClick={() => onNavigate(it.id)}>
          <span className="ic">{it.icon}</span>
          <span>{it.label}</span>
          {it.id === 'problems' && <span className="bottomnav-dot" />}
        </button>
      ))}
    </nav>
  );
}

function ModeBadge({ paperTrading }) {
  // paperTrading === undefined → still loading; render nothing to avoid
  // flashing a false "LIVE" before the health probe completes.
  if (paperTrading === undefined || paperTrading === null) return null;
  if (paperTrading === true) return <PaperBadge />;
  // LIVE — distinct critical-toned chip so it's never mistaken for the orange paper one.
  return (
    <span
      className="paper-badge mono"
      style={{
        color: 'var(--critical)',
        background: 'color-mix(in oklch, var(--critical) 15%, transparent)',
        borderColor: 'color-mix(in oklch, var(--critical) 35%, transparent)',
      }}
      title="실거래 모드"
    >
      <span
        className="pulse-dot"
        style={{
          display: 'inline-block', width: 6, height: 6, borderRadius: 99,
          background: 'var(--critical)', marginRight: 6, verticalAlign: 'middle',
        }}
      />
      LIVE
    </span>
  );
}

function Topbar({ theme, onThemeChange, lastSync, page, onLogout }) {
  // /api/health is public + tiny; poll lightly so a server-side mode flip
  // surfaces in the chrome within ~minute. The Sidenav also polls /api/system/state
  // (authenticated) for the heavier resource block.
  const healthQ = useApi(() => API.health(), [], { refreshMs: 60000 });
  const paperTrading = healthQ.data ? healthQ.data.paper_trading : undefined;

  const pageLabel = {
    home: '홈',
    cycles: '사이클',
    portfolio: '포트폴리오',
    orders: '주문',
    problems: '문제 로그',
    schedule: '스케줄',
    news: '뉴스',
    memory: '메모리',
    backtest: '백테스트',
    system: '시스템',
    more: '더보기',
  }[page] || '';

  const pageSub = {
    home: '오늘의 봇 운영 상태',
    cycles: 'LLM 사이클 이력',
    portfolio: '보유 자산 및 시계열',
    orders: '예약 / 체결 / 자동 실행',
    problems: '심각도별 시스템 이슈',
  }[page] || '';

  return (
    <header className="topbar">
      <div className="topbar-brand">
        <div className="topbar-brand-mark only-mobile">L</div>
        <div className="topbar-page">
          <div className="topbar-page-name">{pageLabel}</div>
          {pageSub && <div className="topbar-page-sub hide-mobile">{pageSub}</div>}
        </div>
      </div>
      <div className="topbar-actions">
        <div className="topbar-status hide-mobile mono">
          <span style={{ color: 'var(--text-3)' }}>last sync</span>
          <span style={{ color: 'var(--text)' }}>{fmtTimeShort(lastSync)}</span>
        </div>
        <ModeBadge paperTrading={paperTrading} />
        <ThemeToggle theme={theme} onChange={onThemeChange} />
        {onLogout && (
          <button className="icon-btn hide-mobile" onClick={onLogout} aria-label="로그아웃" title="로그아웃">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
              <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
              <path d="M16 17l5-5-5-5"/>
              <path d="M21 12H9"/>
            </svg>
          </button>
        )}
      </div>
    </header>
  );
}

function MorePage({ onNavigate }) {
  const items = [
    ...NAV_ITEMS.filter(i => !['home','cycles','portfolio','orders'].includes(i.id)),
    ...NAV_ITEMS_2,
  ];
  return (
    <div className="home" style={{ maxWidth: 600, margin: '0 auto', width: '100%' }}>
      <div className="page-head"><div className="page-head-left"><h1>더보기</h1><div className="page-head-sub mono">/more · 확장 페이지</div></div></div>
      <div className="card" style={{ padding: 8 }}>
        {items.map(it => (
          <button key={it.id} className="more-row" onClick={() => onNavigate(it.id)}>
            <span className="more-row-ic">{it.icon}</span>
            <span style={{ flex: 1, textAlign: 'left' }}>{it.label}</span>
            {it.dot && <span className="navitem-badge dot" />}
            <span style={{ color: 'var(--text-4)', fontSize: 14 }}>›</span>
          </button>
        ))}
      </div>
    </div>
  );
}

function Placeholder({ page }) {
  return (
    <div className="home" style={{ paddingTop: 40 }}>
      <div className="empty" style={{ padding: '60px 24px', flexDirection: 'column', gap: 12 }}>
        <span style={{ fontSize: 24 }}>📋</span>
        <div style={{ color: 'var(--text-2)', fontSize: 14 }}>「{page}」 페이지</div>
      </div>
    </div>
  );
}

// Each page fetches its own data via the useApi hook — no shared mock state.
const PAGES = {
  home:      (nav) => <Home onNavigate={nav} />,
  cycles:    (nav) => <CyclesPage onNavigate={nav} />,
  portfolio: (nav) => <PortfolioPage onNavigate={nav} />,
  orders:    (nav) => <OrdersPage onNavigate={nav} />,
  problems:  (nav) => <ProblemsPage onNavigate={nav} />,
  schedule:  (nav) => <SchedulePage onNavigate={nav} />,
  news:      (nav) => <NewsPage onNavigate={nav} />,
  memory:    (nav) => <MemoryPage onNavigate={nav} />,
  backtest:  (nav) => <BacktestPage onNavigate={nav} />,
  system:    (nav) => <SystemPage onNavigate={nav} />,
};

const VALID_PAGES = new Set([
  'home', 'cycles', 'portfolio', 'orders', 'problems',
  'schedule', 'news', 'memory', 'backtest', 'system', 'more',
]);

// Hash routing: the page lives in location.hash (#/news) so a refresh restores
// the current view and each page gets a shareable URL. Falls back to 'home'.
function pageFromHash() {
  const raw = (window.location.hash || '').replace(/^#\/?/, '').trim();
  return VALID_PAGES.has(raw) ? raw : 'home';
}

function App() {
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [page, setPage] = useAppState(pageFromHash);

  // Keep page state in sync with the URL hash (covers browser back/forward and
  // manual edits), and normalize an empty hash to the resolved page on boot.
  useAppEffect(() => {
    if (!window.location.hash) {
      window.history.replaceState(null, '', '#/' + page);
    }
    function onHash() { setPage(pageFromHash()); }
    window.addEventListener('hashchange', onHash);
    return () => window.removeEventListener('hashchange', onHash);
  }, []);

  function navigate(id) {
    const next = VALID_PAGES.has(id) ? id : 'home';
    window.location.hash = '#/' + next;  // fires hashchange when it actually changes
    setPage(next);                       // immediate update; idempotent with the listener
  }
  // lastSync is a wall-clock heartbeat for the Topbar — real freshness is per-card
  // (each useApi hook re-fetches on its own schedule). Tick every 30s.
  const [lastSync, setLastSync] = useAppState(() => new Date());
  useAppEffect(() => {
    const id = setInterval(() => setLastSync(new Date()), 30000);
    return () => clearInterval(id);
  }, []);
  // null = session check pending; true / false = resolved.
  // Initial guess from sessionStorage avoids the login flash for returning
  // visitors with a still-valid cookie; the /api/auth/session call is the
  // authoritative source.
  const [authed, setAuthed] = useAppState(() => {
    try { return sessionStorage.getItem('lstl_authed') === '1' ? null : false; }
    catch (_) { return false; }
  });

  // Check existing cookie session on boot
  useAppEffect(() => {
    let cancelled = false;
    fetch('/api/auth/session', { credentials: 'same-origin' })
      .then((r) => r.json())
      .then((d) => {
        if (cancelled) return;
        if (d && d.authenticated) {
          try { sessionStorage.setItem('lstl_authed', '1'); } catch (_) {}
          setAuthed(true);
        } else {
          try { sessionStorage.removeItem('lstl_authed'); } catch (_) {}
          setAuthed(false);
        }
      })
      .catch(() => { if (!cancelled) setAuthed(false); });
    return () => { cancelled = true; };
  }, []);

  // Listen for 401 broadcasts from the API client (cookie expired mid-session)
  useAppEffect(() => {
    function onUnauth() {
      try { sessionStorage.removeItem('lstl_authed'); } catch (_) {}
      setAuthed(false);
    }
    window.addEventListener('lstl:unauthenticated', onUnauth);
    return () => window.removeEventListener('lstl:unauthenticated', onUnauth);
  }, []);

  // apply theme to document
  useAppEffect(() => {
    document.documentElement.setAttribute('data-theme', tweaks.theme);
  }, [tweaks.theme]);

  // apply accent
  useAppEffect(() => {
    if (tweaks.accent) {
      document.documentElement.style.setProperty('--accent', tweaks.accent);
    }
  }, [tweaks.accent]);

  if (authed === null) {
    // session check still pending — keep screen blank to avoid login flash
    return <div className="app-boot" aria-hidden />;
  }

  if (!authed) {
    return (
      <LoginPage onSuccess={() => {
        try { sessionStorage.setItem('lstl_authed', '1'); } catch (_) {}
        setAuthed(true);
      }} />
    );
  }

  return (
    <div className="app" data-density={tweaks.density}>
      <Sidenav active={page} onNavigate={navigate} />
      <Topbar
        theme={tweaks.theme}
        onThemeChange={(v) => setTweak('theme', v)}
        lastSync={lastSync}
        page={page}
        onLogout={async () => {
          try { await fetch('/api/auth/logout', { method: 'POST', credentials: 'same-origin' }); }
          catch (_) {}
          try { sessionStorage.removeItem('lstl_authed'); } catch (_) {}
          setAuthed(false);
        }}
      />
      <main className="main">
        {PAGES[page] ? PAGES[page](navigate) : <MorePage onNavigate={navigate} />}
      </main>
      <BottomNav active={page} onNavigate={navigate} />

      <TweaksPanel title="Tweaks">
        <TweakSection label="Appearance" />
        <TweakRadio
          label="Theme"
          value={tweaks.theme}
          options={['dark', 'light']}
          onChange={(v) => setTweak('theme', v)}
        />
        <TweakColor
          label="Accent"
          value={tweaks.accent}
          options={['#5cc3e6', '#7a9af0', '#7c5af2', '#1fb39b', '#e8c259']}
          onChange={(v) => setTweak('accent', v)}
        />
        <TweakSection label="Layout" />
        <TweakRadio
          label="Density"
          value={tweaks.density}
          options={['compact', 'regular', 'comfy']}
          onChange={(v) => setTweak('density', v)}
        />
        <TweakSection label="Mode" />
        <TweakToggle
          label="PAPER 배너"
          value={tweaks.showPaperBanner}
          onChange={(v) => setTweak('showPaperBanner', v)}
        />
      </TweaksPanel>
    </div>
  );
}

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