// <Root> — root of the Preact rewrite. Wires `Header` + `StatusChips`
// to the real `/api/state` snapshot via `useAgentState`. Covers
// everything `app.js` did, including the `needs_login_idle`/
// `needs_login_in_progress` recovery flow (`LoginFlow`) — an agent with
// no credentials has no other web-UI path back online, so this isn't
// optional polish.
import { useEffect, useRef, useState } from 'preact/hooks';
import type { BadgeTone } from '@hive/shared/badge.js';
import { SettingsMenu } from '@hive/shared/settings-menu.js';
import { useApplyThemeOverride } from '@hive/shared/theme-apply.js';
import { useApplyMotionOverride } from '@hive/shared/motion-apply.js';
import { Header } from './components/Header.js';
import { MetaNav } from './components/MetaNav.js';
import { StatusChips } from './components/StatusChips.js';
import { LiveStream, type LiveStreamHandle } from './components/LiveStream.js';
import { HeaderPill } from './components/HeaderPill.js';
import { SidePanel } from './components/SidePanel.js';
import { InboxPanel } from './components/InboxPanel.js';
import { TodosPanel } from './components/TodosPanel.js';
import { TermInput } from './components/TermInput.js';
import { LoginFlow } from './components/LoginFlow.js';
import { useAgentState } from './hooks/useAgentState.js';
import { useTodos } from './hooks/useTodos.js';
import { fmtAge, fmtTokens } from './lib/format.js';
import { resolveDashboardBase } from './lib/dashboardBase.js';
import { submitPauseResume } from './lib/pauseAction.js';
import { postModel, postEffort } from './lib/modelEffort.js';
import type { TokenUsage } from './types.js';

type OpenPanel = 'inbox' | 'todos' | null;

// Storage keys this page owns for `@hive/shared`'s settings mechanism —
// kept here, not derived, so `useApplyThemeOverride`/`useApplyMotionOverride`
// (mounted once below) and `<SettingsMenu>` (in `pills`, further down)
// always agree on which browser-local key they're reading/writing.
const THEME_KEY = 'agent:theme-override';
const MOTION_KEY = 'agent:motion-override';

const ALIVE_LABELS: Record<string, { glyph: string; text: string; tone: BadgeTone }> = {
  online: { glyph: '●', text: 'alive', tone: 'positive' },
  rate_limited: { glyph: '⊘', text: 'rate limited', tone: 'warning' },
  needs_login_idle: { glyph: '◌', text: 'needs login', tone: 'warning' },
  needs_login_in_progress: { glyph: '◌', text: 'logging in', tone: 'warning' },
};
const STATE_LABELS: Record<string, { glyph: string; text: string; tone: BadgeTone }> = {
  idle: { glyph: '💤', text: 'idle', tone: 'neutral' },
  thinking: { glyph: '🧠', text: 'thinking', tone: 'accent' },
  compacting: { glyph: '📦', text: 'compacting', tone: 'warning' },
};
const STATE_TOOLTIPS: Record<string, string> = {
  offline: 'harness unreachable or claude not logged in',
  idle: 'turn loop running, no claude invocation in flight',
  thinking: 'claude is executing the current turn',
  compacting: "operator-triggered /compact running on the persistent session",
};

// `total` here matches app.js's `renderOneUsage` exactly: input + both
// cache buckets, output_tokens deliberately excluded (it's shown in the
// title breakdown, not folded into the headline number).
function tokenTotal(u: TokenUsage | null): number | null {
  if (!u) return null;
  return (u.input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0);
}

export function Root() {
  useApplyThemeOverride(THEME_KEY);
  useApplyMotionOverride(MOTION_KEY);
  const { state, refresh } = useAgentState();
  const { todos, refresh: refreshTodos } = useTodos();
  const [openPanel, setOpenPanel] = useState<OpenPanel>(null);
  const liveStreamRef = useRef<LiveStreamHandle>(null);

  // Browser tab title — ports app.js's setHeader logic verbatim: prefer
  // the human display name + hive_name ("iris // pr1ma") so tabs read
  // naturally; fall back to the qualified domain label for multi-hive
  // disambiguation when hive_name is unset, then to plain label for
  // single-hive deploys. Without this every agent's tab reads the
  // static index.html <title> forever — can't tell tabs apart with
  // several open.
  useEffect(() => {
    if (!state) return;
    const tab =
      state.qualified_label && state.qualified_label !== state.label
        ? state.qualified_label
        : state.label;
    document.title = state.hive_name ? `${state.label} // ${state.hive_name}` : `${tab} // hyperhive`;
  }, [state?.label, state?.qualified_label, state?.hive_name]);
  const termInput = (
    <footer class="agent-composer">
      <TermInput
        label={state?.label ?? '…'}
        online={state?.status === 'online'}
        onLocalNote={(text) => liveStreamRef.current?.pushNote(text)}
        onClear={() => liveStreamRef.current?.clear()}
      />
    </footer>
  );

  const pills = (
    <>
      <HeaderPill kind="inbox" icon="📬" label="inbox" count={state?.inbox.length ?? 0} onClick={() => setOpenPanel('inbox')} />
      <HeaderPill kind="todos" icon="📋" label="todos" count={todos.length} onClick={() => setOpenPanel('todos')} />
      {/* Needs a loaded snapshot for its data (links/forge_public_url,
       * dashboard_port) — omitted until the first /api/state resolves,
       * same as the old page's populateOverflowMenu (now deleted —
       * dashboard-link moved here, rebuild removed outright, see
       * MetaNav.tsx's file comment). */}
      {state ? (
        <MetaNav
          links={state.links}
          forgePublicUrl={state.forge_public_url}
          dashboardBase={resolveDashboardBase(state.dashboard_port)}
        />
      ) : null}
      <SettingsMenu themeKey={THEME_KEY} motionKey={MOTION_KEY} />
    </>
  );
  // Kept mounted regardless of `openPanel` (open/closed toggles just the
  // `open` prop) rather than conditionally rendering the whole
  // component — SidePanel's slide/fade CSS transitions only fire on a
  // class change after mount, not on an already-open initial render, so
  // mount-on-open would skip both the open AND the close animation.
  const panelTitle = openPanel === 'inbox' ? `inbox · ${state?.inbox.length ?? 0}` : openPanel === 'todos' ? `todos · ${todos.length}` : '';
  const panel = (
    <SidePanel open={openPanel !== null} title={panelTitle} onClose={() => setOpenPanel(null)}>
      {openPanel === 'inbox' ? (
        <InboxPanel
          rows={state?.inbox ?? []}
          label={state?.label ?? ''}
          dashboardBase={state ? resolveDashboardBase(state.dashboard_port) : ''}
          onCleared={refresh}
        />
      ) : openPanel === 'todos' ? (
        <TodosPanel todos={todos} onCleared={refreshTodos} />
      ) : null}
    </SidePanel>
  );

  if (!state) {
    return (
      <>
        <Header label="…" pills={pills}>
          <StatusChips
            aliveLabel="… connecting"
            aliveTone="neutral"
            stateLabel="… booting"
            stateTone="neutral"
            model=""
            availableModels={[]}
            onSelectModel={() => {}}
            effort=""
            availableEfforts={[]}
            onSelectEffort={() => {}}
            paused={false}
            onTogglePause={() => {}}
          />
        </Header>
        <main className="agent-main">
          <LiveStream ref={liveStreamRef} />
        </main>
        {termInput}
        {panel}
      </>
    );
  }

  const alive = ALIVE_LABELS[state.status] ?? { glyph: '○', text: 'offline', tone: 'negative' as BadgeTone };
  // Mirrors app.js's refreshState: any non-'online' status forces the
  // turn-state badge to 'offline' regardless of the server's last-known
  // turn_state (login/rate-limit takes visual priority over a stale
  // idle/thinking reading from before the harness went offline).
  const effectiveTurnState = state.status === 'online' ? state.turn_state : 'offline';
  const turnDef = STATE_LABELS[effectiveTurnState] ?? { glyph: '○', text: 'offline', tone: 'negative' as BadgeTone };
  const stateAge = fmtAge(Date.now() - state.turn_state_since * 1000);
  const ctx = tokenTotal(state.ctx_usage);
  const cost = tokenTotal(state.cost_usage);

  return (
    <>
      <Header
        label={state.label}
        hiveLabel={[state.swarm_name, state.hive_name].filter(Boolean).join(' / ') || null}
        pills={pills}
      >
        <StatusChips
          aliveLabel={`${alive.glyph} ${alive.text}`}
          aliveTone={alive.tone}
          stateLabel={`${turnDef.glyph} ${turnDef.text} · ${stateAge}`}
          stateTone={turnDef.tone}
          stateTooltip={STATE_TOOLTIPS[effectiveTurnState]}
          model={state.model}
          availableModels={state.available_models}
          onSelectModel={(name) => {
            postModel(name).then(() => refresh());
          }}
          effort={state.effort}
          availableEfforts={state.available_efforts}
          onSelectEffort={(level) => {
            postEffort(level).then(() => refresh());
          }}
          ctxLabel={ctx !== null ? fmtTokens(ctx) : undefined}
          costLabel={cost !== null ? fmtTokens(cost) : undefined}
          paused={state.paused}
          onTogglePause={() => {
            submitPauseResume(resolveDashboardBase(state.dashboard_port), state.label, state.paused ? 'resume' : 'pause');
          }}
        />
      </Header>
      <main className="agent-main">
        {state.status === 'needs_login_idle' || state.status === 'needs_login_in_progress' ? (
          <LoginFlow status={state.status} session={state.session} onRefresh={refresh} />
        ) : null}
        <LiveStream ref={liveStreamRef} onLiveTurnBoundary={refresh} />
      </main>
      {termInput}
      {panel}
    </>
  );
}
