import {
  useCallback,
  useMemo,
  useState,
  type ChangeEvent,
  type ReactNode,
} from 'react'
import {
  createFieldTypeRegistry,
  evaluateConditionalLogic,
  type AnyFieldDefinition,
  type FieldDefinition,
  type FieldPath,
  type FlexibleRow,
  type MediaFieldRef,
  type EntityFieldRef,
  type TermFieldRef,
  type UserFieldRef,
  type MapValue,
  type LinkValue,
  type PasswordRead,
  type PasswordWrite,
  type RepeaterRow,
  type ResolvedAnyFieldDefinition,
  type FieldType,
  type RuntimeFieldValueMap,
} from '@platform-modules/fields'
import { CustomFieldSurface, type ReactFieldSurfaceRegistry } from './surface-registry.js'

export interface FieldValidationIssue {
  readonly path: FieldPath
  readonly message: string
  readonly code?: string
}

export type ReactFieldDefinition = AnyFieldDefinition | ResolvedAnyFieldDefinition
const BUILTIN_FIELD_TYPES:ReadonlySet<FieldType>=createFieldTypeRegistry().builtInTypes

export interface CompleteFieldEditorAdapters {
  readonly createRowId?: () => string
  readonly surfaceRegistry?: ReactFieldSurfaceRegistry
  readonly resolveCloneFields?: (
    definition: FieldDefinition<'clone'>,
  ) => readonly AnyFieldDefinition[]
}

export interface CompleteFieldValuesEditorProps {
  readonly fields: readonly ReactFieldDefinition[]
  readonly value: RuntimeFieldValueMap
  readonly onChange: (value: RuntimeFieldValueMap) => void
  readonly errors?: readonly FieldValidationIssue[]
  readonly disabled?: boolean
  readonly readOnly?: boolean
  readonly adapters?: CompleteFieldEditorAdapters
  readonly legend?: string
  readonly path?: FieldPath
  readonly idPrefix?: string
}

const RESPONSIVE_CSS = `
.fields-react-complete{container-type:inline-size;container-name:fields-react;display:grid;gap:1rem}
.fields-react-complete__grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.75rem}
.fields-react-complete__field{min-width:0;display:grid;gap:.35rem;align-content:start}
.fields-react-complete__field[data-width="full"]{grid-column:1/-1}
.fields-react-complete__row{border:1px solid currentColor;border-radius:.35rem;padding:.75rem;display:grid;gap:.65rem}
.fields-react-complete__actions{display:flex;gap:.4rem;flex-wrap:wrap}
.fields-react-complete__choice{display:flex;gap:.5rem;align-items:center}
.fields-react-complete__error{font-size:.875rem}
.fields-react-complete__instructions{font-size:.875rem}
.fields-react-complete__list{display:grid;gap:.6rem}
.fields-react-complete input,.fields-react-complete textarea,.fields-react-complete select{max-width:100%;box-sizing:border-box}
@container fields-react (max-width:36rem){.fields-react-complete__grid{grid-template-columns:minmax(0,1fr)}.fields-react-complete__field{grid-column:1/-1}.fields-react-complete__actions>button{min-height:2.5rem}}
`

function pathKey(path: FieldPath): string {
  return JSON.stringify(path)
}

function pathId(prefix: string, path: FieldPath): string {
  const encoded = path
    .map((segment) => String(segment).replace(/[^a-zA-Z0-9_-]/g, '_'))
    .join('__')
  return `${prefix}-${encoded || 'root'}`
}

function issuesFor(errors: readonly FieldValidationIssue[], path: FieldPath): readonly FieldValidationIssue[] {
  const key = pathKey(path)
  return errors.filter((issue) => pathKey(issue.path) === key)
}

function childPath(path: FieldPath, segment: string | number): FieldPath {
  return Object.freeze([...path, segment])
}

function defaultRowId(): string {
  return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
    ? crypto.randomUUID()
    : `row-${Date.now()}-${Math.random().toString(36).slice(2)}`
}

function asObject(value: unknown): Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value)
    ? (value as Record<string, unknown>)
    : {}
}

function asString(value: unknown): string {
  return typeof value === 'string' ? value : ''
}

function asNumber(value: unknown): number | '' {
  return typeof value === 'number' && Number.isFinite(value) ? value : ''
}

function emitNumber(event: ChangeEvent<HTMLInputElement>, onChange: (value: unknown) => void) {
  const raw = event.currentTarget.value
  onChange(raw === '' ? undefined : Number(raw))
}

function choiceEntries(field: AnyFieldDefinition): readonly [string, string][] {
  if (!['select', 'checkbox', 'radio', 'buttonGroup'].includes(field.type)) return []
  return Object.entries((field.settings as { choices: Readonly<Record<string, string>> }).choices)
}

function isCompositeField(field: AnyFieldDefinition): boolean {
  return ['checkbox','radio','buttonGroup','group','repeater','flexible','clone','gallery','relationship','entity','entityLink','taxonomy','user'].includes(field.type)
}

function FieldShell({
  field,
  path,
  prefix,
  errors,
  children,
}: {
  field: AnyFieldDefinition
  path: FieldPath
  prefix: string
  errors: readonly FieldValidationIssue[]
  children: ReactNode
}) {
  const id = pathId(prefix, path)
  const fieldIssues = issuesFor(errors, path)
  const errorId = fieldIssues.length ? `${id}-error` : undefined
  const instructionsId = field.instructions ? `${id}-instructions` : undefined
  const describedBy = [instructionsId, errorId].filter(Boolean).join(' ') || undefined
  return (
    <div
      className="fields-react-complete__field"
      data-width={['textarea', 'richText', 'message', 'group', 'repeater', 'flexible', 'clone'].includes(field.type) ? 'full' : undefined}
      data-field-path={pathKey(path)}
    >
      {!['message', 'accordion', 'tab'].includes(field.type) ? (
        isCompositeField(field) ? <div id={`${id}-label`}>{field.label}{field.required ? ' *' : ''}</div> : <label htmlFor={id}>{field.label}{field.required ? ' *' : ''}</label>
      ) : null}
      {field.instructions ? (
        <div id={instructionsId} className="fields-react-complete__instructions">
          {field.instructions}
        </div>
      ) : null}
      <div data-control-id={id} data-described-by={describedBy} {...(isCompositeField(field)?{role:'group','aria-labelledby':`${id}-label`,'aria-describedby':describedBy,'aria-invalid':fieldIssues.length?true:undefined}:{})}>
        {children}
      </div>
      {fieldIssues.length ? (
        <div id={errorId} role="alert" tabIndex={0} className="fields-react-complete__error">
          {fieldIssues.map((issue) => (
            <div key={`${issue.code ?? 'validation'}:${issue.message}`}>{issue.message}</div>
          ))}
        </div>
      ) : null}
    </div>
  )
}

function InputBase({
  field,
  path,
  prefix,
  errors,
  value,
  onChange,
  disabled,
  readOnly,
  rootValues,
  adapters,
  announce,
}: {
  field: AnyFieldDefinition
  path: FieldPath
  prefix: string
  errors: readonly FieldValidationIssue[]
  value: unknown
  onChange: (value: unknown) => void
  disabled: boolean
  readOnly: boolean
  rootValues: RuntimeFieldValueMap
  adapters: CompleteFieldEditorAdapters
  announce: (message: string) => void
}) {
  const id = pathId(prefix, path)
  const fieldIssues = issuesFor(errors, path)
  const describedBy=[field.instructions?`${id}-instructions`:undefined,fieldIssues.length?`${id}-error`:undefined].filter(Boolean).join(' ')||undefined
  const ariaProps = {
    id,
    'aria-invalid': fieldIssues.length ? true : undefined,
    'aria-describedby': describedBy,
    disabled,
    readOnly,
  } as const
  const settings = field.settings as Record<string, unknown>
  const safeOnChange = (next: unknown) => { if (!disabled && !readOnly) onChange(next) }
  const updateObject = (key: string, next: unknown) => safeOnChange({ ...asObject(value), [key]: next })

  if (field.type === 'message') {
    return <div role="note">{String((settings as { text?: unknown }).text ?? field.label)}</div>
  }
  if (field.type === 'accordion') {
    return (
      <details open={(settings as { open?: boolean }).open === true}>
        <summary>{field.label}</summary>
        {(settings as { text?: string }).text ? <p>{(settings as { text?: string }).text}</p> : null}
      </details>
    )
  }
  if (field.type === 'tab') {
    return <div role="separator" aria-label={field.label}>{(settings as { text?: string }).text}</div>
  }
  if (field.type === 'text' || field.type === 'email' || field.type === 'url') {
    return (
      <input
        {...ariaProps}
        type={field.type === 'text' ? 'text' : field.type}
        placeholder={(settings as { placeholder?: string }).placeholder}
        maxLength={(settings as { maxLength?: number }).maxLength}
        value={asString(value)}
        onChange={(event) => safeOnChange(event.currentTarget.value || undefined)}
      />
    )
  }
  if (field.type === 'textarea' || field.type === 'richText') {
    return (
      <textarea
        {...ariaProps}
        rows={field.type === 'textarea' ? (settings as { rows?: number }).rows : 8}
        value={asString(value)}
        onChange={(event) => safeOnChange(event.currentTarget.value)}
      />
    )
  }
  if (field.type === 'number' || field.type === 'range') {
    return (
      <input
        {...ariaProps}
        disabled={disabled || (readOnly && field.type === 'range')}
        type={field.type === 'range' ? 'range' : 'number'}
        min={(settings as { min?: number }).min}
        max={(settings as { max?: number }).max}
        step={(settings as { step?: number }).step}
        value={asNumber(value)}
        onChange={(event) => emitNumber(event, safeOnChange)}
      />
    )
  }
  if (field.type === 'boolean') {
    return (
      <label className="fields-react-complete__choice">
        <input
          {...ariaProps}
          type="checkbox"
          disabled={disabled || readOnly}
          readOnly={undefined}
          checked={value === true}
          onChange={(event) => safeOnChange(event.currentTarget.checked)}
        />
        {value === true
          ? (settings as { onLabel?: string }).onLabel ?? 'On'
          : (settings as { offLabel?: string }).offLabel ?? 'Off'}
      </label>
    )
  }
  if (field.type === 'select') {
    const multiple = (settings as { multiple?: boolean }).multiple === true
    const selected = multiple
      ? Array.isArray(value) ? value.map(String) : []
      : value === null || value === undefined ? '' : String(value)
    return (
      <select
        id={id}
        aria-invalid={fieldIssues.length ? true : undefined}
        aria-describedby={fieldIssues.length ? `${id}-error` : field.instructions ? `${id}-instructions` : undefined}
        disabled={disabled || readOnly}
        multiple={multiple}
        value={selected}
        onChange={(event) => {
          if (multiple) safeOnChange(Array.from(event.currentTarget.selectedOptions, (option) => option.value))
          else safeOnChange(event.currentTarget.value || ((settings as { allowNull?: boolean }).allowNull ? null : undefined))
        }}
      >
        {!multiple ? <option value="">—</option> : null}
        {choiceEntries(field).map(([choice, label]) => <option key={choice} value={choice}>{label}</option>)}
      </select>
    )
  }
  if (field.type === 'checkbox') {
    const selected = new Set(Array.isArray(value) ? value.map(String) : [])
    return (
      <fieldset disabled={disabled || readOnly}>
        <legend className="sr-only">{field.label}</legend>
        {choiceEntries(field).map(([choice, label]) => (
          <label key={choice} className="fields-react-complete__choice">
            <input
              type="checkbox"
              checked={selected.has(choice)}
              onChange={(event) => {
                const next = new Set(selected)
                if (event.currentTarget.checked) next.add(choice)
                else next.delete(choice)
                safeOnChange([...next])
              }}
            />
            {label}
          </label>
        ))}
      </fieldset>
    )
  }
  if (field.type === 'radio' || field.type === 'buttonGroup') {
    const current = value === null || value === undefined ? '' : String(value)
    return (
      <fieldset disabled={disabled || readOnly}>
        <legend className="sr-only">{field.label}</legend>
        {choiceEntries(field).map(([choice, label]) => (
          <label key={choice} className="fields-react-complete__choice">
            <input
              type="radio"
              name={id}
              value={choice}
              checked={current === choice}
              onChange={() => safeOnChange(choice)}
            />
            {label}
          </label>
        ))}
      </fieldset>
    )
  }
  if (field.type === 'password') {
    const write = asObject(value) as Partial<PasswordWrite>
    const read = asObject(value) as Partial<PasswordRead>
    const inputValue = write.operation === 'set' && typeof write.value === 'string' ? write.value : ''
    return (
      <div className="fields-react-complete__list">
        {read.configured === true ? <span>Credential configured</span> : null}
        <input
          {...ariaProps}
          type="password"
          autoComplete="new-password"
          value={inputValue}
          onChange={(event) => safeOnChange(event.currentTarget.value ? { operation: 'set', value: event.currentTarget.value } : { operation: 'unchanged' })}
        />
        <div className="fields-react-complete__actions">
          <button type="button" disabled={disabled || readOnly} onClick={() => safeOnChange({ operation: 'unchanged' })}>Keep existing</button>
          <button type="button" disabled={disabled || readOnly} onClick={() => safeOnChange({ operation: 'clear' })}>Clear password</button>
        </div>
      </div>
    )
  }
  if (field.type === 'oembed') {
    const current = asObject(value)
    return <input {...ariaProps} type="url" value={asString(current.url)} onChange={(event) => safeOnChange({ ...current, url: event.currentTarget.value })} />
  }
  if (field.type === 'link') {
    const current = asObject(value) as Partial<LinkValue>
    return (
      <div className="fields-react-complete__grid">
        <input {...ariaProps} type="url" aria-label={`${field.label} URL`} value={current.url ?? ''} onChange={(event) => updateObject('url', event.currentTarget.value)} />
        <input type="text" aria-label={`${field.label} title`} disabled={disabled} readOnly={readOnly} value={current.title ?? ''} onChange={(event) => updateObject('title', event.currentTarget.value)} />
        <select aria-label={`${field.label} target`} disabled={disabled || readOnly} value={current.target ?? '_self'} onChange={(event) => updateObject('target', event.currentTarget.value)}>
          {((settings as { targets?: readonly string[] }).targets ?? ['_self', '_blank']).map((target) => <option key={target} value={target}>{target}</option>)}
        </select>
      </div>
    )
  }
  if (field.type === 'image' || field.type === 'file') {
    const current = asObject(value) as Partial<MediaFieldRef>
    return (
      <div className="fields-react-complete__grid">
        <input {...ariaProps} type="text" aria-label={`${field.label} id`} placeholder="media id" value={current.id ?? ''} onChange={(event) => safeOnChange(event.currentTarget.value ? { id: event.currentTarget.value, kind: field.type, ...(current.mimeType ? { mimeType: current.mimeType } : {}) } : null)} />
        <input type="text" aria-label={`${field.label} MIME type`} disabled={disabled} readOnly={readOnly} value={current.mimeType ?? ''} onChange={(event) => safeOnChange({ id: current.id ?? '', kind: field.type, ...(event.currentTarget.value ? { mimeType: event.currentTarget.value } : {}) })} />
      </div>
    )
  }
  if (field.type === 'gallery') {
    const items = Array.isArray(value) ? value as MediaFieldRef<'image'>[] : []
    return <StringObjectList field={field} path={path} prefix={prefix} items={items} disabled={disabled || readOnly} announce={announce} onChange={safeOnChange} kind="media" />
  }
  if (field.type === 'entity' || field.type === 'entityLink') {
    const current = asObject(value) as Partial<EntityFieldRef>
    return <EntityRefInputs label={field.label} value={current} disabled={disabled || readOnly} onChange={safeOnChange} />
  }
  if (field.type === 'relationship') {
    const items = Array.isArray(value) ? value as EntityFieldRef[] : []
    return <StringObjectList field={field} path={path} prefix={prefix} items={items} disabled={disabled || readOnly} announce={announce} onChange={safeOnChange} kind="entity" />
  }
  if (field.type === 'taxonomy') {
    const multiple = (settings as { multiple?: boolean }).multiple === true
    if (multiple) {
      const items = Array.isArray(value) ? value as TermFieldRef[] : []
      return <StringObjectList field={field} path={path} prefix={prefix} items={items} disabled={disabled || readOnly} announce={announce} onChange={safeOnChange} kind="term" />
    }
    const current = asObject(value) as Partial<TermFieldRef>
    return <TermRefInputs label={field.label} value={current} disabled={disabled || readOnly} onChange={safeOnChange} />
  }
  if (field.type === 'user') {
    const multiple = (settings as { multiple?: boolean }).multiple === true
    if (multiple) {
      const items = Array.isArray(value) ? value as UserFieldRef[] : []
      return <StringObjectList field={field} path={path} prefix={prefix} items={items} disabled={disabled || readOnly} announce={announce} onChange={safeOnChange} kind="user" />
    }
    const current = asObject(value) as Partial<UserFieldRef>
    return <input {...ariaProps} type="text" value={current.id ?? ''} onChange={(event) => safeOnChange(event.currentTarget.value ? { id: event.currentTarget.value } : null)} />
  }
  if (field.type === 'date' || field.type === 'time') {
    return <input {...ariaProps} type={field.type} value={asString(value)} onChange={(event) => safeOnChange(event.currentTarget.value || null)} />
  }
  if (field.type === 'dateTime') {
    const display = typeof value === 'string' ? value.replace(/Z$/, '').slice(0, 16) : ''
    return <input {...ariaProps} type="datetime-local" value={display} onChange={(event) => safeOnChange(event.currentTarget.value ? new Date(event.currentTarget.value).toISOString() : null)} />
  }
  if (field.type === 'color') {
    return <input {...ariaProps} type="text" inputMode="text" placeholder="#000000" value={asString(value)} onChange={(event) => safeOnChange(event.currentTarget.value || null)} />
  }
  if (field.type === 'icon') {
    const current = asObject(value)
    return (
      <div className="fields-react-complete__grid">
        <input {...ariaProps} type="text" aria-label={`${field.label} set`} value={asString(current.set)} onChange={(event) => updateObject('set', event.currentTarget.value)} />
        <input type="text" aria-label={`${field.label} name`} disabled={disabled} readOnly={readOnly} value={asString(current.name)} onChange={(event) => updateObject('name', event.currentTarget.value)} />
      </div>
    )
  }
  if (field.type === 'map') {
    const current = asObject(value) as Partial<MapValue>
    const numeric = (key: 'latitude' | 'longitude' | 'zoom', raw: string) => updateObject(key, raw === '' ? undefined : Number(raw))
    return (
      <div className="fields-react-complete__grid">
        <input {...ariaProps} type="number" aria-label={`${field.label} latitude`} step="any" value={current.latitude ?? ''} onChange={(event) => numeric('latitude', event.currentTarget.value)} />
        <input type="number" aria-label={`${field.label} longitude`} step="any" disabled={disabled} readOnly={readOnly} value={current.longitude ?? ''} onChange={(event) => numeric('longitude', event.currentTarget.value)} />
        <input type="text" aria-label={`${field.label} address`} disabled={disabled} readOnly={readOnly} value={current.address ?? ''} onChange={(event) => updateObject('address', event.currentTarget.value || undefined)} />
        <input type="number" aria-label={`${field.label} zoom`} step="any" disabled={disabled} readOnly={readOnly} value={current.zoom ?? ''} onChange={(event) => numeric('zoom', event.currentTarget.value)} />
      </div>
    )
  }
  if (field.type === 'group') {
    const fields = (field.settings as { fields: readonly AnyFieldDefinition[] }).fields
    return <FieldsGrid fields={fields} value={asObject(value)} onChange={safeOnChange} errors={errors} disabled={disabled} readOnly={readOnly} adapters={adapters} path={path} prefix={prefix} rootValues={rootValues} announce={announce} />
  }
  if (field.type === 'repeater') {
    const rows = Array.isArray(value) ? value as RepeaterRow[] : []
    const fields = (field.settings as { fields: readonly AnyFieldDefinition[] }).fields
    const min = (field.settings as { min?: number }).min ?? 0
    const max = (field.settings as { max?: number }).max ?? 100
    return (
      <div className="fields-react-complete__list">
        {rows.map((row, index) => {
          const rowPath = childPath(path, row.rowId)
          return (
            <div key={row.rowId} className="fields-react-complete__row" aria-label={`${field.label} row ${index + 1}`}>
              <FieldsGrid fields={fields} value={row.values} onChange={(next) => safeOnChange(rows.map((candidate, candidateIndex) => candidateIndex === index ? { ...candidate, values: next } : candidate))} errors={errors} disabled={disabled} readOnly={readOnly} adapters={adapters} path={rowPath} prefix={prefix} rootValues={rootValues} announce={announce} />
              <RowActions label={`${field.label} row ${index + 1}`} index={index} length={rows.length} disabled={disabled || readOnly} removeDisabled={rows.length<=min} onMove={(direction) => { const next=[...rows]; const target=index+direction; if(target<0||target>=next.length)return; [next[index],next[target]]=[next[target]!,next[index]!]; safeOnChange(next); announce(`${field.label} row moved ${direction < 0 ? 'up' : 'down'}`) }} onRemove={() => { if(rows.length<=min)return; safeOnChange(rows.filter((_, candidateIndex)=>candidateIndex!==index)); announce(`${field.label} row removed`) }} />
            </div>
          )
        })}
        <button type="button" disabled={disabled || readOnly || rows.length >= max} onClick={() => { const rowId=(adapters.createRowId ?? defaultRowId)(); safeOnChange([...rows,{rowId,values:{}}]); announce(`${field.label} row added`) }}>Add {field.label} row</button>
      </div>
    )
  }
  if (field.type === 'flexible') {
    const rows = Array.isArray(value) ? value as FlexibleRow[] : []
    const layouts = (field.settings as { layouts: readonly { key: string; label: string; fields: readonly AnyFieldDefinition[] }[] }).layouts
    const min = (field.settings as { min?: number }).min ?? 0
    const max = (field.settings as { max?: number }).max ?? 100
    return (
      <div className="fields-react-complete__list">
        {rows.map((row, index) => {
          const layout = layouts.find((candidate) => candidate.key === row.layoutKey)
          return (
            <div key={row.rowId} className="fields-react-complete__row" aria-label={`${field.label} ${layout?.label ?? row.layoutKey} row ${index + 1}`}>
              <strong>{layout?.label ?? row.layoutKey}</strong>
              {layout ? <FieldsGrid fields={layout.fields} value={row.values} onChange={(next) => safeOnChange(rows.map((candidate,candidateIndex)=>candidateIndex===index?{...candidate,values:next}:candidate))} errors={errors} disabled={disabled} readOnly={readOnly} adapters={adapters} path={childPath(childPath(path,row.rowId),row.layoutKey)} prefix={prefix} rootValues={rootValues} announce={announce} /> : <div role="alert">Unknown flexible layout {row.layoutKey}</div>}
              <RowActions label={`${field.label} row ${index + 1}`} index={index} length={rows.length} disabled={disabled || readOnly} removeDisabled={rows.length<=min} onMove={(direction)=>{const next=[...rows];const target=index+direction;if(target<0||target>=next.length)return;[next[index],next[target]]=[next[target]!,next[index]!];safeOnChange(next);announce(`${field.label} row moved ${direction<0?'up':'down'}`)}} onRemove={()=>{safeOnChange(rows.filter((_,candidateIndex)=>candidateIndex!==index));announce(`${field.label} row removed`)}} />
            </div>
          )
        })}
        <div className="fields-react-complete__actions">
          {layouts.map((layout) => <button key={layout.key} type="button" disabled={disabled || readOnly || rows.length >= max} onClick={()=>{const rowId=(adapters.createRowId??defaultRowId)();safeOnChange([...rows,{rowId,layoutKey:layout.key,values:{}}]);announce(`${layout.label} layout added`)}}>Add {layout.label}</button>)}
        </div>
      </div>
    )
  }
  if (field.type === 'clone') {
    const resolver = adapters.resolveCloneFields
    if (!resolver) return <div role="alert">Clone fields require an adopter-supplied clone resolver.</div>
    const fields = resolver(field)
    return <FieldsGrid fields={fields} value={asObject(value)} onChange={safeOnChange} errors={errors} disabled={disabled} readOnly={readOnly} adapters={adapters} path={path} prefix={prefix} rootValues={rootValues} announce={announce} />
  }
  return <div role="alert">Unsupported field type {(field as {type:string}).type}</div>
}

function RowActions({label,index,length,disabled,removeDisabled=false,onMove,onRemove}:{label:string;index:number;length:number;disabled:boolean;removeDisabled?:boolean;onMove:(direction:-1|1)=>void;onRemove:()=>void}) {
  return <div className="fields-react-complete__actions">
    <button type="button" aria-label={`Move ${label} up`} aria-disabled={index===0||disabled} disabled={disabled} onClick={()=>{if(index>0)onMove(-1)}}>Move up</button>
    <button type="button" aria-label={`Move ${label} down`} aria-disabled={index===length-1||disabled} disabled={disabled} onClick={()=>{if(index<length-1)onMove(1)}}>Move down</button>
    <button type="button" aria-label={`Remove ${label}`} aria-disabled={disabled||removeDisabled} disabled={disabled||removeDisabled} onClick={onRemove}>Remove</button>
  </div>
}

function EntityRefInputs({label,value,disabled,onChange}:{label:string;value:Partial<EntityFieldRef>;disabled:boolean;onChange:(value:unknown)=>void}) {
  const emit=(entityType:string,id:string)=>onChange(entityType||id?{entityType,id}:null)
  return <div className="fields-react-complete__grid">
    <input type="text" aria-label={`${label} entity type`} disabled={disabled} value={value.entityType??''} onChange={(event)=>emit(event.currentTarget.value,value.id??'')} />
    <input type="text" aria-label={`${label} entity id`} disabled={disabled} value={value.id??''} onChange={(event)=>emit(value.entityType??'',event.currentTarget.value)} />
  </div>
}

function TermRefInputs({label,value,disabled,onChange}:{label:string;value:Partial<TermFieldRef>;disabled:boolean;onChange:(value:unknown)=>void}) {
  const emit=(taxonomy:string,id:string)=>onChange(taxonomy||id?{taxonomy,id}:null)
  return <div className="fields-react-complete__grid">
    <input type="text" aria-label={`${label} taxonomy`} disabled={disabled} value={value.taxonomy??''} onChange={(event)=>emit(event.currentTarget.value,value.id??'')} />
    <input type="text" aria-label={`${label} term id`} disabled={disabled} value={value.id??''} onChange={(event)=>emit(value.taxonomy??'',event.currentTarget.value)} />
  </div>
}

function StringObjectList({field,items,disabled,onChange,kind,announce}:{field:AnyFieldDefinition;path:FieldPath;prefix:string;items:readonly unknown[];disabled:boolean;onChange:(value:unknown)=>void;kind:'media'|'entity'|'term'|'user';announce:(message:string)=>void}) {
  const create=()=>kind==='media'?{id:'',kind:'image'}:kind==='entity'?{entityType:'',id:''}:kind==='term'?{taxonomy:'',id:''}:{id:''}
  const settings=field.settings as {min?:number;max?:number},min=settings.min??0,max=settings.max??100
  return <div className="fields-react-complete__list">
    {items.map((item,index)=>{
      const current=asObject(item)
      const update=(patch:Record<string,unknown>)=>onChange(items.map((candidate,candidateIndex)=>candidateIndex===index?{...asObject(candidate),...patch}:candidate))
      return <div key={`${index}:${String(current.id??'')}`} className="fields-react-complete__row">
        {kind==='entity'?<EntityRefInputs label={`${field.label} item ${index+1}`} value={current} disabled={disabled} onChange={(next)=>onChange(items.map((candidate,candidateIndex)=>candidateIndex===index?next:candidate))}/>:kind==='term'?<TermRefInputs label={`${field.label} item ${index+1}`} value={current} disabled={disabled} onChange={(next)=>onChange(items.map((candidate,candidateIndex)=>candidateIndex===index?next:candidate))}/>:<input type="text" aria-label={`${field.label} item ${index+1} id`} disabled={disabled} value={asString(current.id)} onChange={(event)=>update({id:event.currentTarget.value})}/>}
        <RowActions label={`${field.label} item ${index+1}`} index={index} length={items.length} disabled={disabled} removeDisabled={items.length<=min} onMove={(direction)=>{const next=[...items];const target=index+direction;if(target<0||target>=next.length)return;[next[index],next[target]]=[next[target]!,next[index]!];onChange(next);announce(`${field.label} item moved ${direction<0?'up':'down'}`)}} onRemove={()=>{onChange(items.filter((_,candidateIndex)=>candidateIndex!==index));announce(`${field.label} item removed`)}} />
      </div>
    })}
    <button type="button" disabled={disabled||items.length>=max} onClick={()=>{onChange([...items,create()]);announce(`${field.label} item added`)}}>Add {field.label} item</button>
  </div>
}

function FieldsGrid({fields,value,onChange,errors,disabled,readOnly,adapters,path,prefix,rootValues,announce}:{fields:readonly ReactFieldDefinition[];value:RuntimeFieldValueMap;onChange:(value:RuntimeFieldValueMap)=>void;errors:readonly FieldValidationIssue[];disabled:boolean;readOnly:boolean;adapters:CompleteFieldEditorAdapters;path:FieldPath;prefix:string;rootValues:RuntimeFieldValueMap;announce:(message:string)=>void}) {
  const setField=useCallback((key:string,next:unknown)=>{const current={...value};if(next===undefined)delete current[key];else current[key]=next;onChange(current)},[onChange,value])
  return <div className="fields-react-complete__grid">
    {fields.map((definition)=>{
      const nextPath=childPath(path,definition.key)
      if(!BUILTIN_FIELD_TYPES.has(definition.type as FieldType))return <div key={definition.key} className="fields-react-complete__field" data-width="full"><CustomFieldSurface definition={definition as ResolvedAnyFieldDefinition} value={value[definition.key]} onChange={(next)=>setField(definition.key,next)} path={nextPath} errors={errors} registry={adapters.surfaceRegistry} inputId={pathId(prefix,nextPath)} disabled={disabled} readOnly={readOnly||definition.readOnly===true}/></div>
      const field=definition as AnyFieldDefinition
      if (field.disabled === true) return null
      if (!evaluateConditionalLogic(field.conditionalLogic, rootValues)) return null
      return <FieldShell key={field.key} field={field} path={nextPath} prefix={prefix} errors={errors}>
        <InputBase field={field} path={nextPath} prefix={prefix} errors={errors} value={value[field.key]} onChange={(next)=>setField(field.key,next)} disabled={disabled} readOnly={readOnly||field.readOnly===true} rootValues={rootValues} adapters={adapters} announce={announce}/>
      </FieldShell>
    })}
  </div>
}

export function CompleteFieldValuesEditor({fields,value,onChange,errors=[],disabled=false,readOnly=false,adapters={},legend,path=[],idPrefix='fields-react'}:CompleteFieldValuesEditorProps) {
  const [announcement,setAnnouncement]=useState('')
  const stableAdapters=useMemo(()=>adapters,[adapters])
  const body=<FieldsGrid fields={fields} value={value} onChange={onChange} errors={errors} disabled={disabled} readOnly={readOnly} adapters={stableAdapters} path={path} prefix={idPrefix} rootValues={value} announce={setAnnouncement}/>
  return <div className="fields-react-complete">
    <style>{RESPONSIVE_CSS}</style>
    <div role="status" aria-live="polite" aria-atomic="true" style={{position:'absolute',width:1,height:1,overflow:'hidden',clip:'rect(0 0 0 0)'}}>{announcement}</div>
    {legend?<fieldset disabled={disabled}><legend>{legend}</legend>{body}</fieldset>:body}
  </div>
}
