import { useState } from 'react'; import { Loader2, MessageSquare, Radar, Trash2, Zap } from 'lucide-react'; import { Badge } from './ui/badge'; import { Button } from './ui/button'; import { Card, CardContent, CardHeader, CardTitle } from './ui/card'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from './ui/dialog'; import { Input } from './ui/input'; import { Textarea } from './ui/textarea'; import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select'; import type { GameState, RoundItem } from '@shared/types'; interface Props { game: GameState; /** A jump (the only turn LLM call) is currently in flight — disable the commit. */ busy: boolean; /** * An advisor fetch (a separate LLM call) is in flight. Distinct from `busy`: * it gates only the advisor UI (the "Ask advisor" button + its hint) and must * never disable "Jump forward" or the rest of the turn flow. */ advisorBusy: boolean; /** Advisor brainstorm suggestions from the last request. */ advisors: string[]; /** Queue a free-text action onto the pending round (no turn advance). */ onAddAction: (text: string) => void; /** Queue a diplomatic message onto the pending round (no turn advance). */ onAddDiplomacy: (to: string, message: string) => void; /** Remove one queued item by its index in `game.pending` (no turn advance). */ onRemoveQueueItem: (index: number) => void; onAdvisor: (prompt?: string) => void; /** Commit the whole pending round in a single LLM turn. */ onJump: () => void; /** * Render without the outer card chrome (the surrounding floating window * provides the border and the "Your move" title). Same content, same testids. */ inWindow?: boolean; } /** One-line, display-safe rendering of a queued item (text truncated). */ function describeItem(item: RoundItem, nationName: (id?: string) => string): string { const who = item.kind === 'diplomacy' ? ` to ${nationName(item.to)}` : ''; const text = item.text.length > 72 ? `${item.text.slice(0, 71)}…` : item.text; return `${who ? `${who}:` : 'Action:'} “${text}”`; } /** * The player's command deck ("Your move"). Three tabs — Action, Diplomacy, * Advisor — each with a small "Add" button that queues an item onto the pending * round (these never advance the turn). Below the tabs a visible pending-queue * list — each row has a small trash button that removes that item (with a * confirmation dialog) — and one prominent "Jump forward" button that commits * the whole round in a single LLM turn. While a jump is in flight the commit * is disabled (as is queue editing) and a hint is shown; an empty jump * (nothing queued) is allowed and advances time. */ export default function ActionPanel({ game, busy, advisorBusy, advisors, onAddAction, onAddDiplomacy, onRemoveQueueItem, onAdvisor, onJump, inWindow = false, }: Props) { const [tab, setTab] = useState('action'); const [action, setAction] = useState(''); const [target, setTarget] = useState(''); const [message, setMessage] = useState(''); const [prompt, setPrompt] = useState(''); // Index of the queued item the user is about to remove, or null (dialog closed). const [confirmRemoveIndex, setConfirmRemoveIndex] = useState(null); const others = Object.entries(game.nations) .filter(([id]) => id !== game.playerNationId) .sort(([a], [b]) => a.localeCompare(b)); const nationName = (id?: string) => (id ? (game.nations[id]?.name ?? id) : ''); const queueAction = () => { const text = action.trim(); if (!text || busy) return; onAddAction(text); setAction(''); }; const queueDiplomacy = () => { const msg = message.trim(); if (!msg || !target || busy) return; onAddDiplomacy(target, msg); setMessage(''); }; const applyIdea = (idea: string) => { setAction(idea); setTab('action'); }; const pending = game.pending; // The item the confirmation dialog is about (re-derived from the live list, // so if the queue changes while the dialog is open it tracks the current // item at that index — or the dialog closes if the index went out of range). const removeTarget = confirmRemoveIndex !== null ? pending[confirmRemoveIndex] : undefined; const queuedLabel = busy ? 'Simulating…' : `Jump forward${pending.length > 0 ? ` · ${pending.length} queued` : ''}`; const header = inWindow ? null : (
Your move
); const body = ( <> Action Diplomacy Advisor