import { UnknownNodeError } from './errors'; import { parseChartRule } from './schema'; import { type ComparisonOperandNode, type Control, type RuleNode } from './types'; /** * Turn a rule into the shape the settings panel renders: a label plus its * editable parts. Accepts a `{$rule}` placeholder or a bare node. Reads the same * value `evaluate` runs — and validates it the same way — so a malformed or * unknown node throws a `ChartRuleError` here too, never a native `TypeError`. * The Valibot parser rejects malformed or nested values before this flat * formatter reads them. */ export function describe(rule: unknown): Control { const parsedRule = parseChartRule(rule); if ('$rule' in parsedRule) { return visit(parsedRule.$rule); } return visit(parsedRule); } function visit(node: RuleNode): Control { switch (node.kind) { case 'value': return { role: 'ref', label: "this row's value" }; case 'literal': { const scalar = node.value; return { role: 'literal', label: String(scalar), value: scalar }; } case 'equals': return comparison('is', node.left, node.right); case 'isNot': return comparison('is not', node.left, node.right); case 'lessThan': return comparison('is under', node.left, node.right); case 'greaterThan': return comparison('is over', node.left, node.right); case 'lessOrEqual': return comparison('is at most', node.left, node.right); case 'greaterOrEqual': return comparison('is at least', node.left, node.right); case 'ifElse': return { role: 'rule', label: 'when', parts: [visit(node.cond), literalControl(node.then.value), literalControl(node.else.value)], }; default: return unreachable(node); } } function comparison(label: string, left: ComparisonOperandNode, right: ComparisonOperandNode): Control { return { role: 'comparison', label, parts: [operandControl(left), operandControl(right)] }; } function operandControl(node: ComparisonOperandNode): Control { return node.kind === 'value' ? { role: 'ref', label: "this row's value" } : literalControl(node.value); } function literalControl(value: string | number | boolean | null): Control { return { role: 'literal', label: String(value), value }; } function unreachable(node: never): never { throw new UnknownNodeError((node as { kind?: string }).kind); }