import { useMemo, useState, type ReactNode } from "react";
import { QuestionnaireEngine, type FieldDefinition, type QuestionDefinition, type QuestionnaireDefinition, type Answers } from "../lib/questionnaire/questionnaire-engine";
import { QuestionnaireRenderer } from "../lib/react/QuestionnaireRenderer";
import { mockSave, mockUpload } from "./mock-persistence";
import { JsonViewer } from "./JsonViewer";
import { SchemaDiagram, type DiagramSelection } from "./SchemaDiagram";
import { ValidationEditor } from "./ValidationEditor";
import starter from "./editor-questionnaire.json";
const basic = starter as QuestionnaireDefinition;
const types: QuestionDefinition["type"][] = ["text", "number", "date", "boolean", "select", "multiselect", "files"];
const json = (value: unknown) => JSON.stringify(value, null, 2);
function Input({ label, value, onChange }: { label: string; value: string; onChange: (value: string) => void }) {
return ;
}
function MoveButtons({ index, count, move, remove, name }: { index: number; count: number; move: (offset: number) => void; remove: () => void; name: string }) {
return
;
}
function reordered(items: T[], index: number, offset: number) {
const next = [...items];
[next[index], next[index + offset]] = [next[index + offset], next[index]];
return next;
}
function FieldsEditor({ fields, update, newId, selectedIndex }: { fields: FieldDefinition[]; update: (fields: FieldDefinition[]) => void; newId: () => string; selectedIndex?: number }) {
function replace(index: number, field: FieldDefinition) { update(fields.map((item, i) => i === index ? field : item)); }
return
{fields.map((field, index) => selectedIndex !== undefined && index !== selectedIndex ? null :
{field.type === "group" ? "Group" : `Question ${index + 1}`}
update(reordered(fields, index, offset))} remove={() => update(fields.filter((_, i) => i !== index))} />
{field.type === "group" ? <>
replace(index, { ...field, title })} />
replace(index, { ...field, when: when || undefined })} />
replace(index, { ...field, fields: children })} newId={newId} />
> : <>
replace(index, { ...field, id })} />
replace(index, { ...field, label })} />
replace(index, { ...field, description: description || undefined })} />
replace(index, { ...field, when: when || undefined })} />
replace(index, { ...field, requiredWhen: requiredWhen || undefined })} />
replace(index, next)} />
{(field.type === "select" || field.type === "multiselect") && }
>}
)}
{selectedIndex === undefined &&
}
;
}
export function SchemaEditorPage() {
const [definition, setDefinition] = useState(() => structuredClone(basic));
const [source, setSource] = useState(() => json(basic));
const [tab, setTab] = useState<"Visual" | "JSON">("Visual");
const [selection, setSelection] = useState();
const [playing, setPlaying] = useState();
const [run, setRun] = useState(0);
const [result, setResult] = useState<{ answers: Answers; outcomes: Record }>();
const validation = useMemo(() => {
try { return { definition: new QuestionnaireEngine(JSON.parse(source)).definition, error: "" }; }
catch (cause) { return { definition: undefined, error: cause instanceof Error ? cause.message : String(cause) }; }
}, [source]);
function update(next: QuestionnaireDefinition) { setDefinition(next); setSource(json(next)); }
function selectTab(next: "Visual" | "JSON") {
if (next === "Visual") {
if (!validation.definition) return;
setDefinition(validation.definition);
setSelection(undefined);
}
setTab(next);
}
function newId() {
const ids = new Set();
function collect(fields: FieldDefinition[]) { fields.forEach(field => field.type === "group" ? collect(field.fields) : ids.add(field.id)); }
definition.pages.forEach(page => collect(page.fields));
let index = 1;
while (ids.has(`question${index}`)) index++;
return `question${index}`;
}
let content: ReactNode;
if (playing) {
content =
Preview · {playing.title}
{result ? Questionnaire completed
Your preview answers and outcomes:
:
setResult({ answers, outcomes })} />
}
;
} else content =
{(["Visual", "JSON"] as const).map(mode => )}
{validation.error ? <>
Fix the schema to play{tab === "JSON" ? " or open Visual" : ""}.{validation.error}> :
✓ Ready to play · {validation.definition!.pages.length} page{validation.definition!.pages.length === 1 ? "" : "s"}}
{tab === "JSON" ?
Edit the full schema, including validation rules, routes, derived values, and outcomes.
:
}
;
return ;
}