'use client'; import { useId, useState } from 'react'; import type { Action, GameView } from '@/game/engine'; import { splitLocation, territory } from '@/game/board'; import { DISCOVERY_LOCATION_BY_ID } from '@/game/discoveries'; import { specialForceName } from '@/game/combat'; import { discoveryEntryDeclineAction, discoveryEntryMoveAction, } from '@/game/discovery-entry-options'; import type { DiscoveryEntrySource } from '@/game/discovery-entry'; import { Button } from './ui/button'; import { Input } from './ui/input'; type Props = { game: GameView; act: (action: Action) => void; busy: boolean; }; type Counts = Record; export function DiscoveryEntryDecision({ game, act, busy }: Props) { const decision = game.decision, offer = game.discoveryEntry; if ( decision?.kind !== 'discoveryEntry' || decision.player !== game.me || !offer || offer.owner !== game.me || offer.event !== decision.event ) return null; return ( ); } function DiscoveryEntryChoice({ game, act, busy }: Props) { const controlId = useId(); const offer = game.discoveryEntry!; const [counts, setCounts] = useState(() => Object.fromEntries( offer.sources.map((source) => [ source.source, { normal: source.normal, elite: source.elite }, ]), ), ); const me = game.players.find((player) => player.id === game.me)!; const eliteName = specialForceName(me.faction); const groups: DiscoveryEntrySource[] = offer.sources.flatMap((source) => { const selected = counts[source.source] ?? { normal: 0, elite: 0 }; return selected.normal + selected.elite > 0 ? [{ source: source.source, ...selected }] : []; }); const amount = groups.reduce( (sum, group) => sum + group.normal + group.elite, 0, ); const move = discoveryEntryMoveAction(game, groups); const decline = discoveryEntryDeclineAction(game); const disabled = busy || !!me.autopilot || !!offer.blocked; const setCount = ( source: DiscoveryEntrySource, kind: 'normal' | 'elite', value: number, ) => { const maximum = source[kind]; const next = Number.isFinite(value) ? Math.max(0, Math.min(maximum, Math.floor(value))) : 0; setCounts((current) => ({ ...current, [source.source]: { ...(current[source.source] ?? { normal: 0, elite: 0 }), [kind]: next, }, })); }; const selectAll = (selected: boolean) => setCounts( Object.fromEntries( offer.sources.map((source) => [ source.source, selected ? { normal: source.normal, elite: source.elite } : { normal: 0, elite: 0 }, ]), ), ); return (

Enter {DISCOVERY_LOCATION_BY_ID[offer.destination].name}

Move any number of your non-advisor forces from{' '} {territory(offer.parent).name} into this newly revealed location. This free entry happens before the storm and does not use shipment or movement.

{offer.blocked && (

{offer.blocked}

)}
{offer.sources.map((source) => { const selected = counts[source.source] ?? { normal: 0, elite: 0 }; const where = splitLocation(source.source); const sourceId = `${controlId}-${source.source.replace(/[^a-z0-9_-]/gi, '-')}`; return (
{territory(where.territory).name} · sector {where.sector}

Available: {source.normal} ordinary {source.elite > 0 ? ` · ${source.elite} ${eliteName}` : ''}

{source.elite > 0 && ( )}
); })}
); }