'use client'; import type { ReactElement } from 'react'; import { parseMovementExpression, MOVEMENT_EXPRESSION_MAX_DEPTH, MOVEMENT_EXPRESSION_MAX_LEAVES, type MovementExpression, } from '@/game/movement-expression-promises'; import { Button } from './ui/button'; import { Input } from './ui/input'; type TerritoryChoice = { id: string; name: string }; export function movementExpressionInputError(value: unknown): string | null { try { parseMovementExpression(value); return null; } catch (error) { return error instanceof Error ? error.message : 'Choose a valid ground-movement expression.'; } } // Structural edits preserve repairable destination and numeric drafts. The // detached valid placeholders are used only to check tree shape and limits. const structuralDraft = (node: MovementExpression): MovementExpression => node.kind === 'move' ? { ...node, territory: 'carthag', minimum: 1 } : { ...node, terms: node.terms.map(structuralDraft) }; const inputSize = (node: MovementExpression): { leaves: number; depth: number } => { if (node.kind === 'move') return { leaves: 1, depth: 0 }; const sizes = node.terms.map(inputSize); return { leaves: sizes.reduce((sum, child) => sum + child.leaves, 0), depth: 1 + Math.max(0, ...sizes.map(child => child.depth)) }; }; /** Hook-free controlled tree. The server decides future route feasibility. */ export function MovementExpressionFields({ value, territories, disabled, onChange }: { value: MovementExpression; territories: readonly TerritoryChoice[]; disabled: boolean; onChange: (expression: MovementExpression) => void; }) { const newLeaf = (): MovementExpression => ({ kind: 'move', territory: territories[0]?.id ?? 'carthag', minimum: 1 }); const size = inputSize(value), error = movementExpressionInputError(value); const commit = (next: MovementExpression) => { if (!disabled && !movementExpressionInputError(structuralDraft(next))) onChange(next); }; const render = (node: MovementExpression, path: string, replace: (next: MovementExpression) => MovementExpression, remove?: () => MovementExpression): ReactElement => { const id = `truth-movement-expression-${path}`; const allowed = (next: MovementExpression) => !disabled && !movementExpressionInputError(structuralDraft(replace(next))); const change = (next: MovementExpression) => commit(replace(next)); const wrap = (kind: 'and' | 'or'): MovementExpression => ({ kind, terms: [node, newLeaf()] }); return
; }; return ; }