import { useCallback, useId, useRef, useState, type CSSProperties, type FormEvent } from 'react'
import type { FieldDef, FieldError, FormDef } from '@platform-modules/forms'
import {
  Field,
  Input,
  Textarea,
  Checkbox,
  RadioGroup,
  Radio,
} from '@platform-modules/ui-primitives'
import { useFormSubmit } from './useFormSubmit.js'
import type { SubmitFn } from './seams.js'

export interface FormRendererProps {
  form: FormDef
  submit: SubmitFn
  renderToken?: string
  onSuccess?: (id: string) => void
}

function initialValue(field: FieldDef): unknown {
  if (field.default !== undefined) return field.default
  switch (field.type) {
    case 'checkbox':
      return false
    case 'multiselect':
      return [] as string[]
    case 'number':
      return ''
    case 'hidden':
      return field.default ?? ''
    default:
      return ''
  }
}

function errorsByField(errors: FieldError[]): Record<string, FieldError> {
  const map: Record<string, FieldError> = {}
  for (const err of errors) {
    map[err.field] = err
  }
  return map
}

const honeypotStyle: CSSProperties = {
  position: 'absolute',
  left: '-9999px',
  width: 1,
  height: 1,
  overflow: 'hidden',
}

const selectClassName =
  'w-full border border-border bg-surface text-fg rounded-md focus-visible:outline focus-visible:outline-accent disabled:opacity-50 disabled:cursor-not-allowed text-base px-3 py-2'

export function FormRenderer({ form, submit, renderToken, onSuccess }: FormRendererProps) {
  const { submit: doSubmit, pending, fieldErrors } = useFormSubmit(submit)
  const [values, setValues] = useState<Record<string, unknown>>(() => {
    const init: Record<string, unknown> = {}
    for (const field of form.fields) {
      init[field.name] = initialValue(field)
    }
    return init
  })
  const [localErrors, setLocalErrors] = useState<Record<string, FieldError>>({})
  const [submitError, setSubmitError] = useState<string | null>(null)
  const [summaryCount, setSummaryCount] = useState(0)
  const alertRef = useRef<HTMLDivElement>(null)

  const honeypotName = form.antispam?.honeypot
  const [honeypot, setHoneypot] = useState('')

  const fieldErrorMap = { ...errorsByField(fieldErrors), ...localErrors }

  const setValue = useCallback((name: string, value: unknown) => {
    setValues((prev) => ({ ...prev, [name]: value }))
    setLocalErrors((prev) => {
      if (!prev[name]) return prev
      const next = { ...prev }
      delete next[name]
      return next
    })
  }, [])

  const renderField = (field: FieldDef) => {
    const error = fieldErrorMap[field.name]?.message
    const value = values[field.name]

    if (field.type === 'hidden') {
      return (
        <input
          key={field.name}
          type="hidden"
          name={field.name}
          value={String(value ?? '')}
        />
      )
    }

    switch (field.type) {
      case 'text':
      case 'email':
      case 'url':
      case 'tel':
      case 'number':
      case 'date':
        return (
          <Field key={field.name} label={field.label} required={field.required} error={error}>
            <Input
              name={field.name}
              type={field.type}
              required={field.required}
              value={value === '' || value === undefined ? '' : String(value)}
              onChange={(e) =>
                setValue(
                  field.name,
                  field.type === 'number' ? (e.target.value === '' ? '' : Number(e.target.value)) : e.target.value,
                )
              }
            />
          </Field>
        )

      case 'textarea':
        return (
          <Field key={field.name} label={field.label} required={field.required} error={error}>
            <Textarea
              name={field.name}
              required={field.required}
              value={String(value ?? '')}
              onChange={(e) => setValue(field.name, e.target.value)}
            />
          </Field>
        )

      case 'select': {
        const controlId = `field-${field.name}`
        return (
          <Field
            key={field.name}
            label={field.label}
            htmlFor={controlId}
            required={field.required}
            error={error}
          >
            <select
              id={controlId}
              name={field.name}
              required={field.required}
              className={selectClassName}
              value={String(value ?? '')}
              onChange={(e) => setValue(field.name, e.target.value)}
              aria-invalid={error ? true : undefined}
              aria-describedby={error ? `${controlId}-error` : undefined}
            >
              <option value="">Select…</option>
              {(field.options ?? []).map((opt) => (
                <option key={opt.value} value={opt.value}>
                  {opt.label}
                </option>
              ))}
            </select>
          </Field>
        )
      }

      case 'multiselect': {
        const selected = Array.isArray(value) ? (value as string[]) : []
        const labelId = `field-${field.name}-label`
        const errorId = error ? `field-${field.name}-error` : undefined
        return (
          <div
            key={field.name}
            role="group"
            aria-labelledby={labelId}
            aria-describedby={errorId}
            className="flex flex-col gap-1"
          >
            <span id={labelId} className="text-sm font-medium text-fg">
              {field.label}
              {field.required ? <span aria-hidden="true"> *</span> : null}
            </span>
            <div className="flex flex-col gap-2">
              {(field.options ?? []).map((opt) => {
                const optId = `${field.name}-${opt.value}`
                const checked = selected.includes(opt.value)
                return (
                  <label key={opt.value} htmlFor={optId} className="flex items-center gap-2 text-sm">
                    <Checkbox
                      id={optId}
                      checked={checked}
                      onCheckedChange={(v) => {
                        const next = v === true
                          ? [...selected, opt.value]
                          : selected.filter((s) => s !== opt.value)
                        setValue(field.name, next)
                      }}
                    />
                    {opt.label}
                  </label>
                )
              })}
            </div>
            {error ? (
              <span id={errorId} role="alert" className="text-sm text-danger">
                {error}
              </span>
            ) : null}
          </div>
        )
      }

      case 'checkbox':
        return (
          <Field key={field.name} label={field.label} required={field.required} error={error}>
            <Checkbox
              checked={Boolean(value)}
              onCheckedChange={(v) => setValue(field.name, v === true)}
            />
          </Field>
        )

      case 'radio': {
        const labelId = `field-${field.name}-label`
        const errorId = error ? `field-${field.name}-error` : undefined
        return (
          <div key={field.name} className="flex flex-col gap-1">
            <span id={labelId} className="text-sm font-medium text-fg">
              {field.label}
              {field.required ? <span aria-hidden="true"> *</span> : null}
            </span>
            <RadioGroup
              name={field.name}
              value={String(value ?? '')}
              onValueChange={(v) => setValue(field.name, v)}
              aria-labelledby={labelId}
              aria-describedby={errorId}
            >
              {(field.options ?? []).map((opt) => {
                const optId = `${field.name}-${opt.value}`
                return (
                  <label key={opt.value} htmlFor={optId} className="flex items-center gap-2 text-sm">
                    <Radio id={optId} value={opt.value} />
                    {opt.label}
                  </label>
                )
              })}
            </RadioGroup>
            {error ? (
              <span id={errorId} role="alert" className="text-sm text-danger">
                {error}
              </span>
            ) : null}
          </div>
        )
      }

      default: {
        const _exhaustive: never = field.type
        return _exhaustive
      }
    }
  }

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault()
    setSubmitError(null)
    setLocalErrors({})
    setSummaryCount(0)

    const data: Record<string, unknown> = { ...values }
    if (honeypotName) {
      data[honeypotName] = honeypot
    }

    try {
      const result = await doSubmit(form.id, data, renderToken)
      if (result.ok) {
        onSuccess?.(result.id)
      } else {
        setLocalErrors(errorsByField(result.errors))
        setSummaryCount(result.errors.length)
        // defer focus until the summary region has rendered
        requestAnimationFrame(() => alertRef.current?.focus())
      }
    } catch (err: unknown) {
      const message = err instanceof Error ? err.message : 'Submission failed'
      setSubmitError(message)
      requestAnimationFrame(() => alertRef.current?.focus())
    }
  }

  const tokenInputId = useId()

  return (
    <form onSubmit={(e) => void handleSubmit(e)} noValidate>
      {renderToken ? (
        <input type="hidden" name="_renderToken" id={tokenInputId} value={renderToken} readOnly />
      ) : null}

      {honeypotName ? (
        <input
          type="text"
          name={honeypotName}
          value={honeypot}
          onChange={(e) => setHoneypot(e.target.value)}
          aria-hidden="true"
          tabIndex={-1}
          autoComplete="off"
          style={honeypotStyle}
        />
      ) : null}

      {form.fields.map(renderField)}

      {submitError || summaryCount > 0 ? (
        <div ref={alertRef} role="alert" tabIndex={-1} className="text-sm text-danger mt-2">
          {submitError ?? `Please fix ${summaryCount} field${summaryCount === 1 ? '' : 's'} below.`}
        </div>
      ) : null}

      <button type="submit" disabled={pending} className="mt-4 px-4 py-2 border rounded">
        {pending ? 'Submitting…' : 'Submit'}
      </button>
    </form>
  )
}
