'use client';
import { useState } from 'react';
import Image from 'next/image';
import { Dialog as DialogPrimitive } from '@base-ui/react/dialog';
import { faction, type FactionId } from '@/game/catalog';
import type { Action, GameView, PlanField } from '@/game/engine';
import { HOMEWORLD_CARDS } from '@/game/homeworld-cards';
import { nexusCardMode, type NexusCardMode } from '@/game/nexus-cards';
import type { NexusCardChoice } from '@/game/nexus-card-phase';
import { nexusCardAction } from '@/game/nexus-card-options';
import { NEXUS_CARD_REFERENCE, NEXUS_PANEL_NAMES, nexusCardReference } from '@/game/nexus-card-reference';
import { Button } from './ui/button';
import { NexusEmperorSecretAlly } from './nexus-emperor-secret-ally';
import { NexusFremenRevival } from './nexus-fremen-revival';
import {NexusTleilaxuRevival} from './nexus-tleilaxu-revival';
import { Dialog, DialogClose, DialogDescription, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger } from './ui/dialog';
const panels: NexusCardMode[] = ['betrayal', 'cunning', 'secretAlly'];
/** Only an authorized projected card may be passed as a private held card. */
export function NexusCardFace({ card, mode, enlarged = false }: {
card: FactionId; mode?: NexusCardMode; enlarged?: boolean;
}) {
const identity = faction(card);
const reference = nexusCardReference(card);
const world = HOMEWORLD_CARDS.find((candidate) => candidate.faction === card)!;
return (
Nexus card
{identity.name}
{panels.map((panel) => (
{NEXUS_PANEL_NAMES[panel]}
{mode === panel && Your applicable mode}
{panel === 'cunning' ? `${identity.name} player only` : panel === 'betrayal' ? `Another player controls ${identity.name}` : `${identity.name} is not in this game`}
{reference[panel]}
))}
{!enlarged && (
)}
);
}
export function NexusCardGallery() {
return
{NEXUS_CARD_REFERENCE.map((card) => )}
;
}
/** The server supplies current choices. The client binds the exact offered
* turn and held identity; it neither infers an opponent's card nor draws one. */
export function nexusCardChoiceAction(game: GameView, choice: NexusCardChoice, ownRedraws: 0 | 1 | 2 = 0): Action | null {
const offer = game.nexusCards;
if (!offer || game.status !== 'playing' || game.phase !== 1 || offer.turn !== game.turn ||
!offer.waiting.includes(game.me) || !offer.choices.includes(choice) || ![0, 1, 2].includes(ownRedraws)) return null;
const owner = game.players.find((player) => player.id === game.me);
if (!owner || (choice === 'draw' && offer.card !== null) ||
(choice === 'replace' && offer.card === null)) return null;
return nexusCardAction(game, choice, ownRedraws);
}
/** The server's private offer defines current fields and timing; no other
* player's hand or sealed plan participates in client eligibility. */
export function nexusAtreidesAction(game: GameView, field?: PlanField): Action | null {
const offer = game.nexusAtreides;
if (!offer || offer.blocked || game.status !== 'playing' || game.phase !== 6 ||
game.nexusCards?.card !== 'atreides') return null;
const owner = game.players.find((player) => player.id === game.me);
if (!owner || nexusCardMode('atreides', owner.faction, game.players.map((player) => player.faction)) !== offer.mode) return null;
const battle = game.battle;
if (!battle || battle.event !== offer.event || battle.revealed) return null;
if (offer.mode === 'betrayal')
return field === undefined ? { type: 'nexusAtreides', event: offer.event, mode: offer.mode } : null;
if (!field || !offer.fields.includes(field) || ![battle.attacker, battle.defender].includes(game.me) || battle.submitted.includes(game.me)) return null;
return { type: 'nexusAtreides', event: offer.event, mode: offer.mode, field };
}
export function NexusCards({ game, act, busy }: { game: GameView; act: (action: Action) => void; busy: boolean }) {
const [ownRedraws, setOwnRedraws] = useState<0 | 1 | 2>(0);
const offer = game.nexusCards;
if (!offer) return null;
const owner = game.players.find((player) => player.id === game.me);
if (!owner) return null;
const mode = offer.card ? nexusCardMode(offer.card, owner.faction, game.players.map((player) => player.faction)) : undefined;
const labels: Record = {
draw: 'Draw a Nexus card', replace: 'Discard and replace',
keep: offer.card ? 'Keep this card' : 'Skip this draw',
};
const waiting = offer.waiting.filter((id) => id !== game.me)
.map((id) => game.players.find((player) => player.id === id)?.name).filter(Boolean);
return (
Nexus cards
{offer.deckCount} in deck · {offer.discardCount} discarded · {Object.values(offer.held).filter((held) => held === true).length} held
Connected controls include Atreides inspections, Harkonnen exchanges, native Cunning, bounded Ecaz Duke acquisition, Emperor bank purchases and extra revivals, Tleilaxu free-leader/paid-force revival, Fremen ordinary free revival or worm protection, Bene Gesserit borrowed Voice, and Richese/Guild shipment effects. Use only the controls offered at each effect’s timing; provisional interpretations are labelled there. {game.nexusKullPreview ? 'CHOAM Nexus Kull is reactive: choose its fuel at the Karama opportunity.' : 'CHOAM Nexus Kull is unavailable outside its explicit preview.'} Moritani Cunning enhances supply placement, not relocation. Unsupported combinations and complete public modes remain gated.
{game.nexusAtreides.mode === 'betrayal'
? 'Discard this Nexus card to cancel the pending Atreides inspection.'
: game.nexusAtreides.mode === 'cunning'
? 'Discard this Nexus card to inspect a second, different element of your opponent’s plan.'
: 'Discard this Nexus card to inspect one element of your opponent’s plan.'}