'use client'; import type { ReactElement } from 'react'; import type { GameView } from '@/game/engine'; import type { PlanClaim } from '@/game/battle-promises'; import { BATTLE_CLAIM_MAX_DEPTH, BATTLE_CLAIM_MAX_NODES, BATTLE_CLAIM_MAX_TERMS, battleClaimExpressionError, battleClaimExpressionSize, } from '@/game/battle-claim-expression'; import { BattleClaimFields } from './battle-promises'; import { Button } from './ui/button'; const newClaim = (): PlanClaim => ({ kind: 'dial', compare: 'eq', value: 0 }); /** Controlled structure editor. Invalid leaf input stays available for repair. */ export function BattleClaimExpressionFields({ value, game, disabled, onChange }: { value: PlanClaim; game: GameView; disabled: boolean; onChange: (claim: PlanClaim) => void; }) { const size = battleClaimExpressionSize(value); const commit = (next: PlanClaim) => { if (!disabled && !battleClaimExpressionError(next)) onChange(next); }; const render = ( node: PlanClaim, path: string, replace: (next: PlanClaim) => PlanClaim, remove?: () => PlanClaim, ): ReactElement => { const group = node.kind === 'and' || node.kind === 'or'; const allowed = (next: PlanClaim) => !disabled && !battleClaimExpressionError(replace(next)); const change = (next: PlanClaim) => commit(replace(next)); const wrap = (kind: 'and' | 'or'): PlanClaim => ({ kind, terms: [node, newClaim()] }); return
{group ? `Group ${path} · ${node.kind.toUpperCase()}` : `Plan condition ${path}`} {group ? <> {node.terms.map((term, index) => render( term, `${path}.${index + 1}`, next => replace({ ...node, terms: node.terms.map((old, i) => i === index ? next : old) }), () => { const terms = node.terms.filter((_, i) => i !== index); return replace(terms.length === 1 ? terms[0] : { ...node, terms }); }, ))}
: }
{remove && }
; }; return
Battle-plan expression

AND requires all conditions; OR requires at least one. The player answers once for the whole expression. No makes the whole expression false, not necessarily every condition.

A definite answer commits the player to a legal battle plan that keeps the whole answer. Available answers and the sealed plan are checked by the game.

App limits: {BATTLE_CLAIM_MAX_TERMS} terms per group, {BATTLE_CLAIM_MAX_NODES} total clauses including groups, and {BATTLE_CLAIM_MAX_DEPTH} levels of grouping. These are input limits, not additional printed rules.

{size.nodes} / {BATTLE_CLAIM_MAX_NODES} total clauses · {size.depth} / {BATTLE_CLAIM_MAX_DEPTH} levels of grouping

{render(value, '1', next => next)}
; }