"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateDiscriminatedUnionFields = generateDiscriminatedUnionFields;
const generator_1 = require("@postxl/generator");
const utils_1 = require("@postxl/utils");
// Tanstack form has this notion of 'nested' forms that might be worth
// exploring in the future:
//
//   https://tanstack.com/form/latest/docs/framework/react/guides/form-composition#breaking-big-forms-into-smaller-pieces
//
// The way the docs talk about this makes it seem like its a requirement to use
// their `createFormHook` utility but I don't have time to work out how that works
// so for now we're just using the standard `useForm` hook.
function generateDiscriminatedUnionFields({ discriminatedUnion, context, }) {
    const imports = new generator_1.ImportGenerator({ path: '' });
    imports.addImports({
        [(0, generator_1.toPackageName)('@tanstack/react-form')]: [(0, generator_1.toFunctionName)('useForm')],
        [(0, generator_1.toPackageName)('react')]: [(0, generator_1.toFunctionName)('useState')],
        [(0, generator_1.toPackageName)('zod')]: [(0, generator_1.toFunctionName)('z')],
        [discriminatedUnion.type.type.location]: [discriminatedUnion.type.type.name],
        [(0, generator_1.toPackageName)('@postxl/ui-components')]: [
            (0, generator_1.toClassName)('Field'),
            (0, generator_1.toClassName)('FieldError'),
            (0, generator_1.toClassName)('FieldGroup'),
            (0, generator_1.toClassName)('FieldLabel'),
            (0, generator_1.toClassName)('FieldLegend'),
            (0, generator_1.toClassName)('FieldSet'),
            (0, generator_1.toClassName)('Select'),
            (0, generator_1.toClassName)('SelectContent'),
            (0, generator_1.toClassName)('SelectItem'),
            (0, generator_1.toClassName)('SelectTrigger'),
            (0, generator_1.toClassName)('SelectValue'),
        ],
    });
    const commonFields = Array.from(discriminatedUnion.field.commonFields.values().map((field) => generateFormField({ context, imports, field })));
    const discriminatedFields = new Map(discriminatedUnion.field.members
        .entries()
        .map(([discriminant, member]) => [
        discriminant,
        Array.from(member.fields.values().map((field) => generateFormField({ context, imports, field }))),
    ]));
    return `
  ${imports.generate()}

  export type ${discriminatedUnion.type.type.name}FieldProps = Readonly<{
    value: ${discriminatedUnion.type.type.name}FieldInput | null;
    onChange: (value: ${discriminatedUnion.type.type.name} ) => void;
    label: string
  }>

  const zCommonFields = {
    ${commonFields.map((field) => `${field.key}: ${field.schema},`).join('\n    ')}
  }

  const schema = z.discriminatedUnion("${discriminatedUnion.field.discriminatorFieldName}", [
    ${Array.from(discriminatedFields.entries())
        .map(([discriminant, fields]) => `z.object({ ...zCommonFields,
      ${discriminatedUnion.field.discriminatorFieldName}: z.literal('${discriminant}'),
      ${fields.map((field) => `${field.key}: ${field.schema},`).join('\n      ')}
    })`)
        .join(',\n    ')}
  ])

  export const z${discriminatedUnion.type.type.name}Field = schema

  type ${discriminatedUnion.type.type.name}FieldInput = z.input<typeof schema>

  const defaultValues = {
    ${[
        ...commonFields,
        {
            key: discriminatedUnion.field.discriminatorFieldName,
            defaultValue: `'${Array.from(discriminatedUnion.field.members.keys())[0]}'`,
        },
        ...Array.from(discriminatedFields.values())[0],
    ]
        .map((field) => `${field.key}: ${field.defaultValue},`)
        .join('\n    ')}
  } as ${discriminatedUnion.type.type.name}FieldInput

  export const z${discriminatedUnion.type.type.name}DefaultValues = defaultValues

  export function ${discriminatedUnion.type.type.name}Field(
    { value, onChange, label }: ${discriminatedUnion.type.type.name}FieldProps
  ) {
    const [${discriminatedUnion.field.discriminatorFieldName}, set${(0, utils_1.toPascalCase)(discriminatedUnion.field.discriminatorFieldName)}] =
      useState(value?.${discriminatedUnion.field.discriminatorFieldName} ?? null)

    const form = useForm({
      defaultValues: value ?? defaultValues,
      validators: {
        // This first validator is the actual zod schema validation for the fieldset.
        onChange: schema,
        onChangeAsyncDebounceMs: 50,
        // This second 'async' validator actually just notifies the parent form
        // of changes. This is actually a hack really, we should work out proper
        // nested forms as per the tanstack form docs.
        onChangeAsync: ({ value }) => {
          onChange(schema.parse(value))
        }
      },
    })

    return (
      <FieldSet>
        <FieldLegend>{label}</FieldLegend>
        <FieldGroup>
          ${commonFields.map((field) => field.formField).join('\n          ')}
        </FieldGroup>

        <Select value={${discriminatedUnion.field.discriminatorFieldName} ?? undefined} onValueChange={(value: ${discriminatedUnion.type.type.name}['${discriminatedUnion.field.discriminatorFieldName}']) => {
          set${(0, utils_1.toPascalCase)(discriminatedUnion.field.discriminatorFieldName)}(value)
          form.setFieldValue('${discriminatedUnion.field.discriminatorFieldName}', value)
        }}>
          <SelectTrigger>
            {${discriminatedUnion.field.discriminatorFieldName} ?? <SelectValue />}
          </SelectTrigger>

          <SelectContent>
            ${Array.from(discriminatedUnion.field.members.keys())
        .map((discriminant) => `
              <SelectItem key="${discriminant}" value="${discriminant}">
                ${discriminant}
              </SelectItem>
            `)
        .join('')}
          </SelectContent>
        </Select>

          {(() => {
            switch (${discriminatedUnion.field.discriminatorFieldName}) {
              ${Array.from(discriminatedFields.entries())
        .map(([discriminant, fields]) => `
                    case '${discriminant}':
                      return ${fields.length === 0
        ? 'null'
        : `(<FieldGroup>${fields.map((field) => field.formField).join('\n')}</FieldGroup>)`}
                  `)
        .join('')}

              default:
                return null
            }
          })()}
      </FieldSet>
    )
  }
  `;
}
function generateFormFieldCode(field, input) {
    return `
    <form.Field name="${field.name}">
      {(field) => {
        const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid

        return (
          <Field data-invalid={isInvalid}>
            <FieldLabel htmlFor={field.name}>
              ${field.label}
            </FieldLabel>

            ${input}

            {isInvalid && <FieldError errors={field.state.meta.errors} />}
          </Field>
        )
      }}
    </form.Field>
  `;
}
function generateFormField({ context, imports, field, }) {
    switch (field.kind) {
        case 'id':
            throw new Error('Unreachable');
        case 'scalar':
            return generateFormField_Scalar({ context, imports, field });
        case 'enum':
            return generateFormField_Enum({ context, imports, field });
        case 'relation':
            return generateFormField_Relation({ context, imports, field });
        default:
            throw new utils_1.ExhaustiveSwitchCheck(field);
    }
}
function generateFormField_Scalar({ imports, field, }) {
    switch (field.type) {
        case 'string':
            imports.addImport({
                from: (0, generator_1.toPackageName)('@postxl/ui-components'),
                items: [(0, generator_1.toClassName)('Input')],
            });
            return {
                key: field.name,
                schema: `z.string()${field.isRequired ? '' : '.nullable()'}`,
                defaultValue: `''`,
                formField: generateFormFieldCode(field, `
              <Input
                id={field.name}
                name={field.name}
                value={${field.isRequired ? 'field.state.value' : 'field.state.value ?? ""'}}
                onBlur={field.handleBlur}
                onChange={(e) => field.handleChange(e.target.value)}
                aria-invalid={isInvalid}
                autoComplete="off"
              />
            `),
            };
        case 'number':
            imports.addImport({
                from: (0, generator_1.toPackageName)('@postxl/ui-components'),
                items: [(0, generator_1.toClassName)('Input')],
            });
            return {
                key: field.name,
                schema: `z.number()${field.isRequired ? '' : '.nullable()'}`,
                defaultValue: `0`,
                formField: generateFormFieldCode(field, `
              <Input
                type="number"
                id={field.name}
                name={field.name}
                value={${field.isRequired ? 'field.state.value' : 'field.state.value ?? ""'}}
                onBlur={field.handleBlur}
                onChange={(e) => field.handleChange(e.target.valueAsNumber)}
                aria-invalid={isInvalid}
                autoComplete="off"
              />
            `),
            };
        case 'boolean':
            imports.addImport({
                from: (0, generator_1.toPackageName)('@postxl/ui-components'),
                items: [(0, generator_1.toClassName)('Checkbox')],
            });
            return {
                key: field.name,
                schema: `z.boolean()${field.isRequired ? '' : '.nullable()'}`,
                defaultValue: `false`,
                formField: generateFormFieldCode(field, `
              <Checkbox
                id={field.name}
                name={field.name}
                checked={${field.isRequired ? 'field.state.value' : '!!field.state.value'}}
                onBlur={field.handleBlur}
                onCheckedChange={(checked) => field.handleChange(!!checked)}
                aria-invalid={isInvalid}
              />
            `),
            };
        case 'Date':
            return {
                key: field.name,
                schema: `z.date()${field.isRequired ? '' : '.nullable()'}`,
                defaultValue: `${field.isRequired ? 'new Date()' : 'null'}`,
                formField: generateFormFieldCode(field, `
              Not implemented yet
            `),
            };
        default:
            throw new utils_1.ExhaustiveSwitchCheck(field);
    }
}
function generateFormField_Enum({ context, imports, field, }) {
    const enum_ = context.enums.get(field.enumName);
    imports.add(enum_.inputs.select);
    return {
        key: field.name,
        schema: `z.enum([${Array.from(enum_.members.values())
            .map((v) => `'${v.value}'`)
            .join(', ')}])${field.isRequired ? '' : '.nullable()'}`,
        defaultValue: field.isRequired ? `'${Array.from(enum_.members.values())[0]?.value ?? 'null'}'` : `null`,
        formField: generateFormFieldCode(field, `
          <${enum_.inputs.select.name}
            name={field.name}
            value={field.state.value}
            onValueChange={(value) => {
              field.handleBlur()
              field.handleChange(value)}
            }
            aria-invalid={isInvalid}
          />
        `),
    };
}
function generateFormField_Relation({ context, imports, field, }) {
    const model = context.models.get(field.referencedModelName);
    imports.add(model.forms.inputs.select).add(model.types.id.toBranded).add(model.types.id.decoder);
    return {
        key: field.name,
        schema: `${model.types.id.decoder.name}${field.isRequired ? '' : '.nullable()'}`,
        defaultValue: field.isRequired ? `''` : `null`,
        formField: generateFormFieldCode(field, `
          <${model.forms.inputs.select.name}
            name={field.name}
            value={${field.isRequired
            ? `${model.types.id.toBranded.name}(field.state.value)`
            : `field.state.value === null ? null : ${model.types.id.toBranded.name}(field.state.value)`}}
            label={(item) => item.${model.labelField.name}}
            onValueChange={(value) => {
              field.handleBlur()
              field.handleChange(value.id)}
            }
            aria-invalid={isInvalid}
          />
        `),
    };
}
//# sourceMappingURL=fields.generator.js.map