/**
 * App — root layout and state composer of the dashboard.
 *
 * Owns auth + simulation snapshot state, renders the map canvas, the
 * agent windows, and all side panels, and handles error banners and
 * day-handoff/provider-failure packets from the backend.
 *
 * Architecture: consumed by main.jsx; renders SimCanvas, InfoBar,
 * ConversationFeed, EventsPanel, DebugPanel, RosterManager, LoginButton.
 *
 * Design: one component per concern, all fed from a single WebSocket
 * snapshot hook (useSimState).
 */

import { useState, useEffect, useCallback } from "react";
import useSimState from "./hooks/useSimState";
import { AuthProvider, useAuth } from "./hooks/useAuth";
import SimCanvas from "./components/SimCanvas";
import AgentWindow from "./components/AgentWindow";
import InfoBar from "./components/InfoBar";
import ConversationFeed from "./components/ConversationFeed";
import EventsPanel from "./components/EventsPanel";
import DebugPanel from "./components/DebugPanel";
import RosterManager from "./components/RosterManager";
import LoginButton from "./components/LoginButton";
import LogTerminal from "./components/LogTerminal";
import { apiUrl } from "./utils/api";
import "./App.css";

function compactTabPosition(index) {
  const side = index % 2;
  const row = Math.floor(index / 2);
  return {
    // right-hand card can then expand without its controls leaving the view.
    x: side ? Math.max(16, window.innerWidth - 276) : 16,
    y: 66 + row * 92,
  };
}

export default function App() {
  return (
    <AuthProvider>
      <AppContent />
    </AuthProvider>
  );
}

function AppContent() {
  const { token, isAuthenticated } = useAuth();
  const snapshot = useSimState();
  const [renderSnapshot, setRenderSnapshot] = useState(null);
  const [agentMap, setAgentMap] = useState(null);
  const [focusedId, setFocusedId] = useState(null);
  const [expandedAgentIds, setExpandedAgentIds] = useState(() => new Set());
  const [showDebug, setShowDebug] = useState(false);
  const [conversationFeedMinimized, setConversationFeedMinimized] = useState(false);
  const [controlError, setControlError] = useState(null);
  const [simulationRunning, setSimulationRunning] = useState(true);
  const [rosterOpen, setRosterOpen] = useState(false);
  const [showTerminal, setShowTerminal] = useState(false);

  useEffect(() => {
    if (!snapshot) return;
    // A handoff packet is a transient status message without `agents`.
    // Preserve the latest cards until the next full state arrives.
    if (snapshot.type === "day_reset" || snapshot.type === "reset") {
      setAgentMap(null);
      setFocusedId(null);
      setExpandedAgentIds(new Set());
      return;
    }
    if (snapshot.agents) {
      setAgentMap(snapshot.agents);
    }
    // Handoff status packets intentionally omit a full world frame. Keep the
    // last complete frame rendered until the first new-day frame arrives.
    if (snapshot.tick != null && snapshot.time != null && snapshot.day != null) {
      setRenderSnapshot(snapshot);
    }
    if (typeof snapshot.simulation?.running === "boolean") {
      setSimulationRunning(snapshot.simulation.running);
    }
  }, [snapshot]);

  const providerFailure = snapshot?.status === "provider_failure" ? snapshot.failure : null;
  const simulationError = (snapshot?.status === "error" || providerFailure) ? snapshot.message : null;
  const agentIds = agentMap ? Object.keys(agentMap) : [];

  function toggleAgentInspector(agentId) {
    const isExpanded = expandedAgentIds.has(agentId);
    setExpandedAgentIds((previous) => {
      const next = new Set(previous);
      if (next.has(agentId)) next.delete(agentId);
      else next.add(agentId);
      return next;
    });
    if (isExpanded && focusedId === agentId) setFocusedId(null);
    else if (!isExpanded) setFocusedId(agentId);
  }

  const focusFromMap = useCallback((agentId) => {
    setFocusedId(agentId);
    if (agentId) {
      setExpandedAgentIds((previous) => {
        if (previous.has(agentId)) return previous;
        const next = new Set(previous);
        next.add(agentId);
        return next;
      });
    }
  }, []);

  async function sendControl(path, body) {
    try {
      setControlError(null);
      const headers = { "Content-Type": "application/json" };
      if (token) headers["Authorization"] = `Bearer ${token}`;
      const response = await fetch(apiUrl(path), {
        method: "POST",
        headers,
        body: body ? JSON.stringify(body) : undefined,
      });
      const data = await response.json();
      if (!response.ok) throw new Error(data.error || data.detail || "Simulation control failed.");
      return data;
    } catch (error) {
      setControlError(error.message);
      return null;
    }
  }

  return (
    <div className="app-root">
      <SimCanvas snapshot={renderSnapshot || snapshot} focusedId={focusedId} onFocus={focusFromMap} />

      {simulationError && (
        <section className="simulation-error" role="alert" aria-live="assertive">
          <span className="simulation-error__eyebrow">{providerFailure?.title || "Simulation unavailable"}</span>
          <p>{simulationError}</p>
          <small>{providerFailure?.guidance || "Runtime data was left unchanged. Start a fresh simulation when you are ready."}</small>
        </section>
      )}
      {controlError && (
        <section className="simulation-error simulation-error--control" role="status">
          <span className="simulation-error__eyebrow">Timeline control</span>
          <p>{controlError}</p>
        </section>
      )}

      <ConversationFeed
        conversations={snapshot?.recent_conversations}
        minimized={conversationFeedMinimized}
        onToggleMinimized={() => setConversationFeedMinimized((value) => !value)}
      />
      <EventsPanel events={snapshot?.events} />
      {showDebug && <DebugPanel health={snapshot?.health} />}
      <RosterManager open={rosterOpen} onClose={() => setRosterOpen(false)} simulationRunning={simulationRunning} onError={setControlError} isAuthenticated={isAuthenticated} />
      {isAuthenticated && showTerminal && <LogTerminal onClose={() => setShowTerminal(false)} />}

      {agentIds.map((id, index) => {
        const expanded = expandedAgentIds.has(id);
        const expandedLayer = [...expandedAgentIds].indexOf(id);
        return (
          <AgentWindow
            key={id}
            agentId={id}
            data={agentMap[id]}
            speed={snapshot?.speed}
            defaultPosition={compactTabPosition(index)}
            expanded={expanded}
            expandedLayer={expandedLayer}
            onToggle={() => toggleAgentInspector(id)}
          />
        );
      })}

      <InfoBar
        snapshot={renderSnapshot || snapshot}
        showDebug={showDebug}
        onToggleDebug={() => setShowDebug((value) => !value)}
        onFastForward={() => sendControl("/api/sim/fast-forward")}
        onSlowDown={() => sendControl("/api/sim/slow-down")}
        onRewind={(rewind) => sendControl("/api/sim/rewind", rewind)}
        simulationRunning={simulationRunning}
        onToggleSimulation={async () => {
          const result = await sendControl(simulationRunning ? "/api/sim/stop" : "/api/sim/start");
          if (result && typeof result.running === "boolean") setSimulationRunning(result.running);
        }}
        onToggleRoster={() => setRosterOpen((value) => !value)}
        isAuthenticated={isAuthenticated}
        terminalOpen={showTerminal}
        onToggleTerminal={() => setShowTerminal((value) => !value)}
      />
      <LoginButton />
    </div>
  );
}
