'use client'; import type { ReactElement } from 'react'; import { parseBribeExpression, BRIBE_EXPRESSION_MAX_DEPTH, BRIBE_EXPRESSION_MAX_LEAVES, type BribeExpression, } from '@/game/bribe-expression-promises'; import { Button } from './ui/button'; import { Input } from './ui/input'; type Recipient = { id: string; name: string }; // Every new condition requires the author to choose its public recipient. const newLeaf = (): BribeExpression => ({ kind: 'payment', recipient: '', minimum: 1 }); export function bribeExpressionInputError(value: unknown): string | null { try { parseBribeExpression(value); return null; } catch (error) { return error instanceof Error ? error.message : 'Choose a valid ordinary bribe expression.'; } } // Detached placeholders validate only the structure. They allow empty recipient // choices and invalid numeric drafts to remain visible while the author repairs // them; the real tree is always passed intact to the parent. const structuralDraft = (node: BribeExpression): BribeExpression => node.kind === 'payment' ? { ...node, recipient: 'recipient', minimum: 1 } : { ...node, terms: node.terms.map(structuralDraft) }; const inputSize = (node: BribeExpression): { leaves: number; depth: number } => { if (node.kind !== 'and' && node.kind !== 'or') 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 editor. The server owns recipient eligibility, funding * and compliance with the whole answer. */ export function BribeExpressionFields({ value, recipients, disabled, onChange }: { value: BribeExpression; recipients: readonly Recipient[]; disabled: boolean; onChange: (expression: BribeExpression) => void; }) { const size = inputSize(value), error = bribeExpressionInputError(value); const commit = (next: BribeExpression) => { if (!disabled && !bribeExpressionInputError(structuralDraft(next))) onChange(next); }; const render = (node: BribeExpression, path: string, replace: (next: BribeExpression) => BribeExpression, remove?: () => BribeExpression): ReactElement => { const group = node.kind !== 'payment'; const id = `truth-bribe-expression-${path}`; const allowed = (next: BribeExpression) => !disabled && !bribeExpressionInputError(structuralDraft(replace(next))); const change = (next: BribeExpression) => commit(replace(next)); const wrap = (kind: 'and' | 'or'): BribeExpression => ({ kind, terms: [node, newLeaf()] }); return
; }; return ; }