'use client'

/**
 * Top-level admin field for component-aware blocks. Consumes a
 * {@link Manifest} produced by `@actuate-media/component-blocks`,
 * renders a component picker (when more than one component is allowed)
 * and a recursive props form via {@link PropInput}.
 *
 * Storage shape:
 *   { component: 'Hero', props: { title: 'Welcome', alignment: 'center' } }
 *
 * Live validation runs through `validateComponentBlockValue` from
 * `@actuate-media/cms-core`. The whole-value error is shown at the top
 * of the form, and the per-prop error map (parsed out of the message
 * string) is forwarded into `PropInput` so each input can render its
 * own inline error.
 */

import { useEffect, useMemo, useRef, useState } from 'react'

import type { Manifest } from '@actuate-media/component-blocks'

import {
  buildClientValidator,
  getAllowedComponents,
  parsePerPropErrors,
  seedPropsForComponent,
} from './component-block-helpers.js'
import { PropInput } from './PropInput.js'

export interface ComponentBlockValue {
  component: string
  props: Record<string, unknown>
}

export interface ComponentBlockFieldProps {
  label?: string
  manifest: Manifest
  /** Optional whitelist of component names. */
  allow?: string[]
  /** Default component picked when the field is first used. */
  defaultComponent?: string
  value?: ComponentBlockValue
  onChange: (value: ComponentBlockValue) => void
  /**
   * Optional async validator hook. Receives the proposed value and the
   * field config; should return `true` for ok or a string error.
   * Falls back to a built-in structural validator when not provided —
   * which is what cms-core would do server-side at save time anyway.
   */
  validate?: (value: ComponentBlockValue) => string | true
  helpText?: string
}

export function ComponentBlockField({
  label,
  manifest,
  allow,
  defaultComponent,
  value,
  onChange,
  validate,
  helpText,
}: ComponentBlockFieldProps) {
  const allowedComponents = useMemo(() => getAllowedComponents(manifest, allow), [manifest, allow])
  // Track the initial seed application so we don't repeatedly stomp
  // the editor's prop values on every render. The defaultComponent
  // seed only fires once, when value is undefined on mount.
  const seededRef = useRef(false)

  useEffect(() => {
    if (seededRef.current) return
    if (value) {
      seededRef.current = true
      return
    }
    const picked =
      allowedComponents.find((c) => c.name === defaultComponent) ?? allowedComponents[0]
    if (!picked) return
    seededRef.current = true
    onChange({ component: picked.name, props: seedPropsForComponent(picked) })
  }, [allowedComponents, defaultComponent, value, onChange])

  const currentSpec =
    (value && manifest.components.find((c) => c.name === value.component)) ?? allowedComponents[0]

  // Pure structural validation when caller didn't provide their own.
  // Keeps the form usable even without a live validator wired in.
  const builtinValidate = useMemo(() => buildClientValidator(manifest, allow), [manifest, allow])
  const validator = validate ?? builtinValidate

  const [errorMessage, setErrorMessage] = useState<string | null>(null)
  const perPropErrors = useMemo(() => parsePerPropErrors(errorMessage), [errorMessage])

  useEffect(() => {
    if (!value) {
      setErrorMessage(null)
      return
    }
    const result = validator(value)
    setErrorMessage(result === true ? null : result)
  }, [value, validator])

  function handleComponentChange(componentName: string) {
    const spec = manifest.components.find((c) => c.name === componentName)
    if (!spec) return
    onChange({ component: spec.name, props: seedPropsForComponent(spec) })
  }

  function handlePropChange(propName: string, next: unknown) {
    if (!value) return
    onChange({
      ...value,
      props: { ...value.props, [propName]: next },
    })
  }

  if (!currentSpec) {
    return (
      <div className="rounded-md border border-[var(--destructive)] bg-red-50 p-3 text-sm text-[var(--destructive)]">
        Component block has no components to choose from.
      </div>
    )
  }

  return (
    <div className="space-y-3">
      {label ? <label className="block text-sm font-medium">{label}</label> : null}

      {allowedComponents.length > 1 ? (
        <label className="block text-sm">
          <div className="mb-1 text-xs tracking-wide text-[var(--muted-foreground)] uppercase">
            Component
          </div>
          <select
            value={currentSpec.name}
            onChange={(e) => handleComponentChange(e.target.value)}
            className="w-full rounded-md border border-[var(--border)] bg-[var(--input-background)] px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-[var(--ring)]"
          >
            {allowedComponents.map((spec) => (
              <option key={spec.name} value={spec.name}>
                {spec.displayName}
              </option>
            ))}
          </select>
        </label>
      ) : null}

      {errorMessage ? (
        <div className="rounded-md border border-[var(--destructive)] bg-red-50 px-3 py-2 text-xs text-[var(--destructive)]">
          {errorMessage}
        </div>
      ) : null}

      <fieldset className="rounded-md border border-[var(--border)] bg-[var(--card)] p-4">
        <legend className="px-1 text-xs tracking-wide text-[var(--muted-foreground)] uppercase">
          {currentSpec.displayName}
        </legend>
        {currentSpec.description ? (
          <p className="mb-3 text-xs text-[var(--muted-foreground)]">{currentSpec.description}</p>
        ) : null}
        <div className="space-y-4">
          {currentSpec.props.map((prop) => (
            <PropInput
              key={prop.name}
              prop={prop}
              value={value?.props?.[prop.name]}
              onChange={(next) => handlePropChange(prop.name, next)}
              errors={perPropErrors}
              path={prop.name}
            />
          ))}
        </div>
      </fieldset>

      {helpText ? <p className="text-xs text-[var(--muted-foreground)]">{helpText}</p> : null}
    </div>
  )
}
