import { createElement, type ComponentType, type ReactNode } from 'react' import { Function as Fn } from 'effect' import { Ui, type MadeView, type UiClass, type UiContract, type ViewImpl } from '@playfast/reform' import type { ArrayBinding, ArrayItem, ArrayPath, FieldBinding, FieldPath, PathValue, Values, VariantBody, VariantPath, VariantTag, VariantValue, View, AnyForm, } from '@playfast/reform-forms' import { joinPath } from '@playfast/reform-forms' export type FieldComponent = ComponentType< { readonly field: FieldBinding } & P > export interface FieldHandle { as(component: FieldComponent): ReactNode as

(component: FieldComponent, props: P): ReactNode } export interface ArrayItemScope extends ScopeHelpers { readonly key: string readonly index: number readonly value: Item readonly remove: () => void readonly move: (to: number) => void } export interface ArrayRenderArgs { readonly items: ReadonlyArray> readonly append: (value?: Item) => void readonly remove: (index: number) => void readonly move: (from: number, to: number) => void readonly swap: (a: number, b: number) => void } type VariantCases = { readonly [Tag in VariantTag]: (scope: ScopeHelpers>) => ReactNode } export interface ScopeHelpers { readonly field:

>( path: P, ) => FieldHandle> readonly array:

>( path: P, render: (args: ArrayRenderArgs>) => ReactNode, ) => ReactNode readonly variant:

>( path: P, cases: VariantCases>, ) => ReactNode } export interface RootHelpers extends ScopeHelpers> { readonly form: View } type ContractWithForm = UiContract & { readonly props: { readonly form: View } } const makeFieldHandle = (field: FieldBinding): FieldHandle => ({ as:

(component: FieldComponent, props?: P): ReactNode => createElement( component, Fn.unsafeCoerce } & P>({ ...props, field }), ), }) interface RuntimeFormView { readonly field: (path: string) => FieldBinding readonly array: (path: string) => ArrayBinding readonly variantValue: (path: string) => Variant } const makeScope = ( form: RuntimeFormView, prefix: string, ): ScopeHelpers => { const full = (path: string): string => joinPath(prefix, path) return { field: (path) => makeFieldHandle(form.field>(full(String(path)))), array: (path, render) => { const binding = form.array>(full(String(path))) return render({ append: binding.append, remove: binding.remove, move: binding.move, swap: binding.swap, items: Fn.unsafeCoerce( binding.items.map((element) => ({ ...makeScope(form, `${binding.path}[${element.index}]`), key: element.key, index: element.index, value: element.value, remove: element.remove, move: element.move, })), ), }) }, variant: (path, cases) => { const variantPath = full(String(path)) const current = form.variantValue(variantPath) if (current === null || typeof current !== 'object' || !('_tag' in current)) { return null } const tag = String(current._tag) const handlers = Fn.unsafeCoerce< typeof cases, Record) => ReactNode> >(cases) const render = handlers[tag] return render === undefined ? null : render(makeScope(form, variantPath)) }, } } export const make = , N extends string>( contract: UiClass, _form: F, render: ( helpers: RootHelpers & { readonly props: C['props'] }, slots: Parameters>[1], events: Parameters>[2], ) => ReactNode, ): MadeView => Ui.make(contract, (props, slots, events) => render( { ...makeScope>(Fn.unsafeCoerce(props.form), ''), form: props.form, props, }, slots, events, ), ) export const FormUi: { readonly make: typeof make } = { make }