import { type Answers, type Bug, type Graph, type Hidden, isRecord, key, type Labels, list, loopOf, must, type Rule, type Shape, sortBy, stateText, type Step, type Story, tupleKey, type Vars, type View, type Walk, } from "../ir.ts"; const isLabel = (l: unknown): l is string => typeof l === "string" && l !== "" && !/\bundefined\b|\bNaN\b|\[object Object\]/.test(l); const shapeOf = (shape: unknown, vars: Vars): Shape => { function fail(): never { throw new Error( `The view's state() returned ${JSON.stringify(shape) ?? "undefined"} for the state ${stateText(vars)}. Return a string, an array of strings, or an object of those.`, ); } const path = (p: unknown) => { const labels: string[] = []; for (const l of [p].flat()) { if (!isLabel(l)) fail(); labels.push(l); } if (!labels.length) fail(); return labels; }; const regions: [string, unknown][] = isRecord(shape) ? Object.entries(shape) : [["", shape]]; const pairs: [string, string[]][] = []; for (const [k, v] of regions) pairs.push([k, path(v)]); return pairs.length ? Object.fromEntries(pairs) : fail(); }; const stepOf = (view: View) => (action: string, from: Vars, to: Vars) => { const label = (view.step ?? ((a: string) => a))(action, from, to); if (label === null || isLabel(label)) return label; throw new Error( `The view's step() returned ${JSON.stringify(label) ?? "undefined"} for the step ${action}.`, ); }; function unlabeled( action: string, before: Vars, after: Vars, bug: { rule: Rule; loop?: number }, ): never { const changed: string[] = []; for (const k of Object.keys(after)) if (key(before[k]) !== key(after[k])) changed.push(k); throw new Error( `The view's step() returned null for the step ${action} that changes ${changed.join(", ") || "nothing"}. It is on the path to the ${bug.rule.name} bug${bug.loop === undefined ? "" : " or leaves its loop"}, so label it.`, ); } export function ask(graph: Graph, { states: tla, nameOf, from, to }: Walk, view: View): Answers { const n = nameOf.length; const m = from.length; const state = view.state; const shapes = new Array(n); for (let i = 0; i < n; i++) shapes[i] = shapeOf(state ? state(tla[i].vars) : nameOf[i], tla[i].vars); const names = Object.keys(shapes[0] ?? { "": [] }); const regionsKey = tupleKey(names); for (let i = 0; i < n; i++) if (tupleKey(Object.keys(shapes[i])) !== regionsKey) throw new Error( `The view's state() returned the regions ${names.join(", ")} for one state and ${Object.keys(shapes[i]).join(", ")} for the state ${stateText(tla[i].vars)}. Return the same regions for every state.`, ); const step = stepOf(view); const labels = new Array