import type { Context, Types } from "effect" import { Effect, Function, Option } from "effect" import * as FormBody from "./FormBody.js" import type * as FormField from "./FormField.js" import * as FormFramework from "./FormFramework.js" import { Path } from "./Path.js" interface ArrayDisplay extends FormFramework.MakeIterable { Element: AnyFieldDisplay } interface MapDisplay extends FormFramework.MakeIterable { Element: & AnyFieldDisplay & FormFramework.MakeMapKey } type RawDisplay = FormFramework.MakeRaw type FieldDisplay = & ReturnType< Context.Tag.Service > & { useControls: () => FormFramework.FieldControls } type FormDisplay = & { [key in keyof Body["fields"]]: AnyFieldDisplay< Body["fields"][key] > } & { useControls: () => FormFramework.FieldControls } type AnyFieldDependencies = Field extends FormField.Any ? Field["tag"] : Field extends FormBody.Any ? FormDisplayDependenciesTag : Field extends FormBody.AnyIterable ? AnyFieldDependencies : never type FormDisplayDependenciesTag = { [key in keyof Body["fields"]]: AnyFieldDependencies }[keyof Body["fields"]] type FormDisplayDependencies = Context.Tag.Identifier> const addControls = (component: T, path: Path) => Effect.gen(function*() { const { makeFieldControls } = yield* FormFramework.FormFramework // @ts-expect-error "'T' could be instantiated with an arbitrary type" const res: T & { useControls: () => FormFramework.FieldControls } = Object.assign( // @ts-expect-error "Argument of type 'T' is not assignable to parameter of type 'object'" component, makeFieldControls(path) ) return res }) const makeField = (field: FormField.Any, path: Path) => { return Effect.gen(function*() { const builder = yield* field.tag const component = builder({ path }) return yield* addControls(component, path) }) } const makeImpl = ( body: Body, path: Path = Path.empty ): Effect.Effect< Types.Simplify>, never, FormDisplayDependencies > => { return Effect.gen(function*() { const framework = yield* FormFramework.FormFramework // @ts-expect-error "{} is incompatible with the desired type" const display: Types.Simplify> = {} for (const [key, fieldValue] of Object.entries(body.fields)) { const currentPath = path.appendString(key) if (FormBody.isFormStruct(fieldValue)) { // @ts-expect-error "key does not exist in display" display[key] = yield* makeImpl(fieldValue, currentPath) } else if ( FormBody.isFormArray(fieldValue) || FormBody.isFormMap(fieldValue) ) { const { field } = fieldValue const defaultValue = "getDefaultValue" in field ? Option.getOrUndefined(field.getDefaultValue()) : field.defaultValue const fieldArray = framework.makeIterable(defaultValue, currentPath) const pathWithIndex = FormBody.isFormMap(fieldValue) ? currentPath.appendIndex().appendString("1") : currentPath.appendIndex() const Element = FormBody.isFormStruct(field) ? yield* makeImpl(field, pathWithIndex) : yield* makeField(field, pathWithIndex) Object.assign(fieldArray, { Element }) if (FormBody.isFormMap(fieldValue)) { Object.assign( Element, framework.makeMapKey( fieldValue.keySchema, currentPath.appendIndex().appendString("0") ) ) } // @ts-expect-error "key does not exist in display" display[key] = fieldArray } else if (FormBody.isFormRaw(fieldValue)) { // @ts-expect-error "key does not exist in display" display[key] = framework.makeRaw(currentPath) } else { // @ts-expect-error "key does not exist in display" display[key] = yield* makeField(fieldValue, currentPath) } } return yield* addControls(display, path) }) } export type AnyFieldDisplay = Field extends FormField.Any ? FieldDisplay : Field extends FormBody.Any ? Types.Simplify> : Field extends FormBody.AnyArray ? ArrayDisplay : Field extends FormBody.AnyMap ? MapDisplay : Field extends FormBody.AnyRaw ? RawDisplay : never // we must add & {} in the type so that Object.assign works correctly const makeObjectAssignable = (value: T) => Function.unsafeCoerce(value) export const make = (body: Body) => { return Effect.gen(function*() { const display = makeObjectAssignable(yield* makeImpl(body)) const framework = yield* FormFramework.FormFramework const Form: FormFramework.FormComponent = framework.makeForm({ schema: body.schema, resetValues: body.defaultValue }) const Submit = framework.makeSubmit(Form.id) const Clear = framework.Clear return Object.assign(display, { Form, Submit, Clear }) }) }