import { useEffect, useMemo, useState } from 'react'
import { useForm, Controller, type Path, type SubmitHandler } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
import Ajv, { type ErrorObject } from 'ajv'
import addFormats from 'ajv-formats'
import { CaretDownIcon, CircleNotchIcon } from '@phosphor-icons/react'
import {
  Alert,
  Badge,
  Button,
  Checkbox,
  DatePicker,
  FieldError,
  FieldHint,
  FieldLabel,
  FormActions,
  SingleSelect,
  Tabs,
  TextArea,
  TextInput,
  type SelectOption,
  type TabItem,
} from '@/ui'
import type { CreateRecordRequest, FlattenedRecord, JsonSchema } from '@/api/client'
import { useReferencedRecords } from '@/api/queries'
import {
  combineDateTime,
  extractTime,
  formatIsoDate,
  parseFormDate,
} from '@/lib/dates'
import { pickLocalized } from '@/lib/locales'

type FormValues = {
  businessKey: string
  validFrom: string
  validTo: string
  data: Record<string, unknown>
}

type Props = {
  schema: JsonSchema
  supportedLocales: string[]
  defaultLocale: string
  defaultValues?: Partial<FormValues>
  mode: 'create' | 'edit'
  isPending: boolean
  serverError?: string | null
  onSubmit: (req: CreateRecordRequest) => void
  onCancel: () => void
  /** When provided, form renders WITHOUT internal Cancel/Save buttons —
   * footer is expected to be drawn by parent (e.g. RecordDrawer). External
   * submit triggered via `<button type="submit" form={formId}>`. */
  formId?: string
  /** Notifies parent of dirty-field count (for "N полей" footer caption). */
  onDirtyChange?: (dirtyCount: number) => void
  /** Layout mode (default 'tabs' для backward compat):
   *  - 'tabs': single bucket visible at once, tab bar для switch (legacy)
   *  - 'sections': all buckets vertically scrollable + section chips strip
   *    на верху для quick jump. Per handoff Screen 3 RecordDrawer design. */
  layout?: 'tabs' | 'sections'
  /** Field name search filter (drawer toolbar). Case-insensitive substring match
   * против labelOf(key) AND key. Empty = no filter. */
  searchFilter?: string
  /** Show only fields с non-empty values (drawer toolbar 'только заполненные').
   * Empty = number 0, '', null, undefined, false, []. */
  onlyFilled?: boolean
  /** Notifies parent of visible/total field counters (for drawer toolbar 'N / M'). */
  onCounterChange?: (visible: number, total: number) => void
}

const ajv = new Ajv({ allErrors: true, strict: false })
addFormats(ajv)

const isLocalized = (s: JsonSchema): boolean => Boolean(s['x-localized'])

const humanize = (key: string): string =>
  key.replace(/[_-]+/g, ' ').replace(/\b\w/g, (ch) => ch.toUpperCase())

const isDateFormat = (s: JsonSchema): boolean =>
  s.type === 'string' && (s.format === 'date' || s.format === 'date-time')

type TabId = 'identity' | 'description' | 'extra'

const bucketProperties = (
  properties: Record<string, JsonSchema>,
  required: Set<string>,
): Record<TabId, string[]> => {
  const buckets: Record<TabId, string[]> = { identity: [], description: [], extra: [] }
  for (const [key, schema] of Object.entries(properties)) {
    if (isLocalized(schema)) {
      buckets.description.push(key)
    } else if (required.has(key)) {
      buckets.identity.push(key)
    } else {
      buckets.extra.push(key)
    }
  }
  return buckets
}

export const SchemaDrivenForm = ({
  schema,
  supportedLocales,
  defaultLocale,
  defaultValues,
  mode,
  isPending,
  serverError,
  onSubmit,
  onCancel,
  formId,
  onDirtyChange,
  layout = 'tabs',
  searchFilter,
  onlyFilled,
  onCounterChange,
}: Props) => {
  const { t } = useTranslation()
  const [activeTab, setActiveTab] = useState<TabId>('identity')

  const validator = useMemo(() => {
    try {
      return ajv.compile(schema)
    } catch {
      return null
    }
  }, [schema])

  const properties = schema.properties ?? {}
  const required = new Set(schema.required ?? [])
  const buckets = useMemo(() => bucketProperties(properties, required), [properties, required])

  const { register, handleSubmit, control, setError, formState } = useForm<FormValues>({
    defaultValues: {
      businessKey: defaultValues?.businessKey ?? '',
      validFrom: defaultValues?.validFrom ?? '',
      validTo: defaultValues?.validTo ?? '',
      data: defaultValues?.data ?? {},
    },
  })

  const labelOf = (k: string, s: JsonSchema): string =>
    s.title ?? t(`field.${k}`, { defaultValue: humanize(k) })

  const idSource = (schema as { 'x-id-source'?: string })['x-id-source']

  // === Drawer toolbar filtering ===
  // search: substring match (case-insensitive) против key OR labelOf(key)
  // onlyFilled: hide fields где data[key] empty (null/undefined/''/false/[]/0)
  // Применяется только в sections layout (drawer use case).
  const dataValues = (mode === 'edit' ? defaultValues?.data : undefined) ?? {}
  const isFieldVisible = useMemo(() => {
    const search = (searchFilter ?? '').trim().toLowerCase()
    return (key: string): boolean => {
      if (search) {
        const label = labelOf(key, properties[key]).toLowerCase()
        if (!key.toLowerCase().includes(search) && !label.includes(search)) return false
      }
      if (onlyFilled) {
        const v = (dataValues as Record<string, unknown>)[key]
        if (v == null || v === '' || v === false || v === 0) return false
        if (Array.isArray(v) && v.length === 0) return false
        if (typeof v === 'object' && Object.keys(v as object).length === 0) return false
      }
      return true
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [searchFilter, onlyFilled, properties, dataValues, t])

  // Apply filter ON TOP of bucketing — каждый bucket после filter содержит
  // visible subset. Used для render + counters.
  const filteredBuckets = useMemo(
    () => ({
      identity: buckets.identity.filter(isFieldVisible),
      description: buckets.description.filter(isFieldVisible),
      extra: buckets.extra.filter(isFieldVisible),
    }),
    [buckets, isFieldVisible],
  )

  // Counter visible/total для drawer toolbar caption "N / M".
  const totalFieldCount = buckets.identity.length + buckets.description.length + buckets.extra.length
  const visibleFieldCount =
    filteredBuckets.identity.length + filteredBuckets.description.length + filteredBuckets.extra.length
  useEffect(() => {
    onCounterChange?.(visibleFieldCount, totalFieldCount)
  }, [visibleFieldCount, totalFieldCount, onCounterChange])

  const submit: SubmitHandler<FormValues> = (values) => {
    if (values.validFrom && values.validTo) {
      const from = new Date(values.validFrom)
      const to = new Date(values.validTo)
      if (!isNaN(from.getTime()) && !isNaN(to.getTime()) && from >= to) {
        setError('validTo', { type: 'manual', message: t('form.error.validToBeforeFrom') })
        setActiveTab('identity')
        return
      }
    }
    if (validator) {
      const ok = validator(values.data)
      if (!ok && validator.errors && validator.errors.length > 0) {
        applyAjvErrors(validator.errors, setError)
        const firstField = firstFieldFromErrors(validator.errors)
        if (firstField) {
          const tabForField = findTabForField(firstField, buckets)
          if (tabForField) setActiveTab(tabForField)
        }
        return
      }
    }
    const derivedKey = idSource && mode === 'create'
      ? String(values.data?.[idSource] ?? '').trim()
      : values.businessKey.trim()
    onSubmit({
      businessKey: derivedKey,
      data: values.data,
      validFrom: values.validFrom || undefined,
      validTo: values.validTo || undefined,
    })
  }

  const tabErrorCount = countErrorsPerTab(formState.errors, buckets)
  const visibleTabs: TabItem[] = (['identity', 'description', 'extra'] as TabId[])
    .filter((id) => id === 'identity' || buckets[id].length > 0)
    .map((id) => ({
      id,
      label: t(`form.tabs.${id}`),
      count:
        tabErrorCount[id] > 0 ? (
          <Badge variant="error">{tabErrorCount[id]}</Badge>
        ) : undefined,
    }))

  // Surface dirty-field count к parent (RecordDrawer footer caption).
  // Используем formState.dirtyFields shallow keys + nested data keys.
  const dirtyCount = useMemo(() => {
    const top = Object.keys(formState.dirtyFields).filter(
      (k) => k !== 'data' && (formState.dirtyFields as Record<string, unknown>)[k],
    ).length
    const dataDirty = formState.dirtyFields.data
      ? Object.keys(formState.dirtyFields.data).length
      : 0
    return top + dataDirty
  }, [formState.dirtyFields])
  useEffect(() => {
    onDirtyChange?.(dirtyCount)
  }, [dirtyCount, onDirtyChange])

  // Section visibility — в 'sections' layout все buckets рендерятся подряд,
  // в 'tabs' — только active. Sections layout per handoff Screen 3 prototype.
  const showAllSections = layout === 'sections'
  const sectionVisible = (id: TabId) => showAllSections || activeTab === id

  // Scroll-to-section для chip click (только в sections mode).
  const scrollToSection = (id: TabId) => {
    document.getElementById(`form-section-${formId ?? 'default'}-${id}`)?.scrollIntoView({
      behavior: 'smooth',
      block: 'start',
    })
  }

  return (
    <form
      id={formId}
      onSubmit={handleSubmit(submit)}
      className="space-y-4"
    >
      {/* Section chips strip per handoff (sections mode) OR tab bar (legacy). */}
      {showAllSections ? (
        <div className="flex flex-wrap items-center gap-1.5 sticky top-0 bg-surface pb-2 -mt-1 z-10">
          {visibleTabs.map((tab) => (
            <button
              key={tab.id}
              type="button"
              onClick={() => scrollToSection(tab.id as TabId)}
              className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-sm bg-surface-2 border border-line hover:bg-accent-bg hover:border-accent text-cap text-ink-2 hover:text-accent transition-colors"
            >
              {tab.label}
              <span className="text-mono text-mute">
                {filteredBuckets[tab.id as TabId].length}
              </span>
            </button>
          ))}
        </div>
      ) : (
        <Tabs items={visibleTabs} value={activeTab} onValueChange={(id) => setActiveTab(id as TabId)} />
      )}

      <div
        id={`form-section-${formId ?? 'default'}-identity`}
        className={sectionVisible('identity') ? 'block scroll-mt-12' : 'hidden'}
      >
        {showAllSections && (
          <h3 className="text-cap text-mute mb-2 sticky top-10 bg-surface py-1 z-[5]">
            {t('form.tabs.identity')}
          </h3>
        )}
        <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
          {!idSource && (
            <div className="sm:col-span-2">
              <TextInput
                label={t('form.businessKey')}
                required
                disabled={mode === 'edit'}
                placeholder="MY_RECORD_KEY"
                error={formState.errors.businessKey ? t('form.error.required') : undefined}
                {...register('businessKey', { required: !idSource, minLength: idSource ? 0 : 1 })}
              />
            </div>
          )}
          {idSource && (
            <div className="sm:col-span-2 text-cell text-mute px-2">
              {t('form.idSourceNote', { field: idSource })}
            </div>
          )}
          <Controller
            control={control}
            name="validFrom"
            render={({ field }) => (
              <DateTimeField
                label={t('form.validFrom')}
                hint={t('form.validFromHint')}
                value={field.value as string | undefined}
                defaultTime="00:00"
                onChange={field.onChange}
              />
            )}
          />
          <Controller
            control={control}
            name="validTo"
            render={({ field }) => (
              <DateTimeField
                label={t('form.validTo')}
                hint={t('form.validToHint')}
                error={(formState.errors.validTo as { message?: string } | undefined)?.message}
                value={field.value as string | undefined}
                defaultTime="23:59"
                onChange={field.onChange}
              />
            )}
          />
          {filteredBuckets.identity.map((key) => (
            <FieldRenderer
              key={key}
              fieldKey={key}
              schema={properties[key]}
              required={required.has(key)}
              control={control}
              supportedLocales={supportedLocales}
              defaultLocale={defaultLocale}
              fieldError={formState.errors.data?.[key as keyof FormValues['data']]}
              labelOf={labelOf}
            />
          ))}
        </div>
      </div>

      <div
        id={`form-section-${formId ?? 'default'}-description`}
        className={sectionVisible('description') ? 'block scroll-mt-12' : 'hidden'}
      >
        {showAllSections && filteredBuckets.description.length > 0 && (
          <h3 className="text-cap text-mute mb-2 sticky top-10 bg-surface py-1 z-[5]">
            {t('form.tabs.description')}
          </h3>
        )}
        <div className="grid grid-cols-1 gap-3">
          {filteredBuckets.description.map((key) => (
            <FieldRenderer
              key={key}
              fieldKey={key}
              schema={properties[key]}
              required={required.has(key)}
              control={control}
              supportedLocales={supportedLocales}
              defaultLocale={defaultLocale}
              fieldError={formState.errors.data?.[key as keyof FormValues['data']]}
              labelOf={labelOf}
            />
          ))}
        </div>
      </div>

      <div
        id={`form-section-${formId ?? 'default'}-extra`}
        className={sectionVisible('extra') ? 'block scroll-mt-12' : 'hidden'}
      >
        {showAllSections && filteredBuckets.extra.length > 0 && (
          <h3 className="text-cap text-mute mb-2 sticky top-10 bg-surface py-1 z-[5]">
            {t('form.tabs.extra')}
          </h3>
        )}
        <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
          {filteredBuckets.extra.map((key) => (
            <FieldRenderer
              key={key}
              fieldKey={key}
              schema={properties[key]}
              required={required.has(key)}
              control={control}
              supportedLocales={supportedLocales}
              defaultLocale={defaultLocale}
              fieldError={formState.errors.data?.[key as keyof FormValues['data']]}
              labelOf={labelOf}
            />
          ))}
        </div>
      </div>

      {serverError && <Alert variant="error">{serverError}</Alert>}

      {/* Когда formId не передан — внутренний footer (backward compat).
       * Когда передан — drawer footer (см. RecordDrawer) рисует кнопки и
       * сабмитит через атрибут form={formId} на <button type="submit">. */}
      {!formId && (
        <FormActions align="end">
          <Button type="button" variant="ghost" onClick={onCancel} disabled={isPending}>
            {t('form.cancel')}
          </Button>
          <Button type="submit" variant="primary" loading={isPending}>
            {isPending ? t('form.saving') : t('form.save')}
          </Button>
        </FormActions>
      )}
    </form>
  )
}

const isFullWidth = (s: JsonSchema): boolean => {
  if (s['x-localized']) return true
  if (s.type === 'array') return true
  if ((s.maxLength ?? 0) > 200) return true
  if (s.type === 'object') return true
  return false
}

type FieldRendererProps = {
  fieldKey: string
  schema: JsonSchema
  required: boolean
  control: ReturnType<typeof useForm<FormValues>>['control']
  supportedLocales: string[]
  defaultLocale: string
  fieldError?: unknown
  labelOf: (key: string, schema: JsonSchema) => string
}

const FieldRenderer = (props: FieldRendererProps) => {
  const wrapperClass = isFullWidth(props.schema) ? 'sm:col-span-2' : ''
  return (
    <div className={wrapperClass}>
      <FieldBody {...props} />
    </div>
  )
}

const FieldBody = ({
  fieldKey,
  schema,
  required,
  control,
  supportedLocales,
  defaultLocale,
  fieldError,
  labelOf,
}: FieldRendererProps) => {
  const label = labelOf(fieldKey, schema)
  const errorMsg = (fieldError as { message?: string } | undefined)?.message

  if (isLocalized(schema)) {
    return (
      <div>
        <FieldLabel required={required}>
          {label}{' '}
          <span className="text-cap text-mute ml-1">i18n</span>
        </FieldLabel>
        <div className="space-y-2 pl-3 border-l-2 border-line mt-1">
          {supportedLocales.map((loc) => (
            <Controller
              key={loc}
              control={control}
              name={`data.${fieldKey}.${loc}` as `data.${string}`}
              render={({ field }) => (
                <div className="flex items-center gap-3">
                  <span className="text-mono text-ink-2 normal-case tracking-normal whitespace-nowrap shrink-0 w-14">
                    {loc === defaultLocale ? <strong>{loc}</strong> : loc}
                  </span>
                  <div className="flex-1 min-w-0">
                    <TextInput {...field} value={(field.value as string | undefined) ?? ''} />
                  </div>
                </div>
              )}
            />
          ))}
        </div>
        {schema.description && <FieldHint>{schema.description}</FieldHint>}
        <FieldError>{errorMsg}</FieldError>
      </div>
    )
  }

  if (schema['x-references']) {
    return (
      <ReferenceSelectField
        fieldKey={fieldKey}
        schema={schema}
        required={required}
        control={control}
        defaultLocale={defaultLocale}
        label={label}
        errorMsg={errorMsg}
      />
    )
  }

  if (schema.enum) {
    const options: SelectOption[] = schema.enum.map((v) => ({
      id: String(v),
      label: String(v),
    }))
    return (
      <Controller
        control={control}
        name={`data.${fieldKey}` as `data.${string}`}
        render={({ field }) => (
          <SingleSelect
            label={label}
            required={required}
            hint={schema.description}
            error={errorMsg}
            options={options}
            value={(field.value as string | undefined) ?? ''}
            onChange={field.onChange}
            placeholder="—"
          />
        )}
      />
    )
  }

  if (schema.type === 'boolean') {
    return (
      <Controller
        control={control}
        name={`data.${fieldKey}` as `data.${string}`}
        render={({ field }) => (
          <Checkbox
            label={label}
            description={schema.description}
            error={errorMsg}
            checked={Boolean(field.value)}
            onChange={(e) => field.onChange(e.target.checked)}
          />
        )}
      />
    )
  }

  if (isDateFormat(schema)) {
    const isDateTime = schema.format === 'date-time'
    if (isDateTime) {
      return (
        <Controller
          control={control}
          name={`data.${fieldKey}` as `data.${string}`}
          render={({ field }) => (
            <DateTimeField
              label={label}
              value={field.value as string | undefined}
              defaultTime="00:00"
              onChange={(iso) => field.onChange(iso || undefined)}
            />
          )}
        />
      )
    }
    return (
      <Controller
        control={control}
        name={`data.${fieldKey}` as `data.${string}`}
        render={({ field }) => (
          <DatePicker
            label={label}
            required={required}
            hint={schema.description}
            error={errorMsg}
            value={parseFormDate(field.value)}
            onChange={(d) => field.onChange(d ? formatIsoDate(d) : undefined)}
          />
        )}
      />
    )
  }

  if (schema.type === 'integer' || schema.type === 'number') {
    return (
      <Controller
        control={control}
        name={`data.${fieldKey}` as `data.${string}`}
        render={({ field }) => (
          <TextInput
            type="number"
            step={schema.type === 'integer' ? 1 : 'any'}
            label={label}
            required={required}
            hint={schema.description}
            error={errorMsg}
            value={(field.value as number | undefined) ?? ''}
            onChange={(e) =>
              field.onChange(e.target.value === '' ? undefined : Number(e.target.value))
            }
          />
        )}
      />
    )
  }

  if (schema.type === 'object') {
    return (
      <Controller
        control={control}
        name={`data.${fieldKey}` as `data.${string}`}
        render={({ field }) => (
          <JsonField
            label={label}
            required={required}
            hint={schema.description}
            error={errorMsg}
            value={field.value as object | undefined}
            onChange={field.onChange}
          />
        )}
      />
    )
  }

  if (schema.type === 'array' && schema.items?.type === 'object') {
    return (
      <Controller
        control={control}
        name={`data.${fieldKey}` as `data.${string}`}
        render={({ field }) => (
          <JsonField
            label={label}
            required={required}
            hint={schema.description ? `${schema.description} (JSON array)` : 'JSON array'}
            error={errorMsg}
            value={field.value as unknown[] | undefined}
            onChange={field.onChange}
          />
        )}
      />
    )
  }

  if (schema.type === 'array' && schema.items?.type === 'string') {
    return (
      <Controller
        control={control}
        name={`data.${fieldKey}` as `data.${string}`}
        render={({ field }) => (
          <TextInput
            label={label}
            required={required}
            hint={schema.description ? `${schema.description} (comma-separated)` : 'comma-separated'}
            error={errorMsg}
            value={Array.isArray(field.value) ? (field.value as string[]).join(', ') : ''}
            onChange={(e) =>
              field.onChange(
                e.target.value
                  .split(',')
                  .map((s) => s.trim())
                  .filter(Boolean),
              )
            }
          />
        )}
      />
    )
  }

  return (
    <Controller
      control={control}
      name={`data.${fieldKey}` as `data.${string}`}
      render={({ field }) => {
        const isLong = (schema.maxLength ?? 0) > 200
        if (isLong) {
          return (
            <TextArea
              label={label}
              required={required}
              hint={schema.description}
              error={errorMsg}
              rows={3}
              {...field}
              value={(field.value as string | undefined) ?? ''}
            />
          )
        }
        return (
          <TextInput
            type={schema.format === 'date-time' ? 'datetime-local' : 'text'}
            label={label}
            required={required}
            hint={schema.description}
            error={errorMsg}
            pattern={schema.pattern}
            {...field}
            value={(field.value as string | undefined) ?? ''}
          />
        )
      }}
    />
  )
}

type DateTimeFieldProps = {
  label: string
  value: string | undefined
  defaultTime: string
  onChange: (iso: string) => void
  hint?: string
  error?: string
}

const DateTimeField = ({ label, value, defaultTime, onChange, hint, error }: DateTimeFieldProps) => {
  const dateValue = parseFormDate(value)
  const timeValue = extractTime(value, defaultTime)
  return (
    <div>
      <div className="grid grid-cols-[1fr_auto] gap-2 items-end">
        <DatePicker
          label={label}
          value={dateValue}
          onChange={(d) => onChange(combineDateTime(d, timeValue, value ?? ''))}
        />
        <div className="w-28">
          <TextInput
            label="HH:mm"
            type="time"
            value={timeValue}
            onChange={(e) => onChange(combineDateTime(dateValue, e.target.value, value ?? ''))}
          />
        </div>
      </div>
      {hint && !error && <p className="text-cell text-mute mt-1">{hint}</p>}
      {error && <p className="text-cell text-mars mt-1">{error}</p>}
    </div>
  )
}

type JsonFieldProps = {
  label: string
  required: boolean
  hint?: string
  error?: string
  value: unknown
  onChange: (v: unknown) => void
}

const JsonField = ({ label, required, hint, error, value, onChange }: JsonFieldProps) => {
  const [text, setText] = useState<string>(() =>
    value === undefined || value === null ? '' : JSON.stringify(value, null, 2),
  )
  const [parseError, setParseError] = useState<string | null>(null)

  return (
    <TextArea
      label={label}
      required={required}
      hint={hint ? `${hint} (JSON)` : 'JSON'}
      error={parseError ?? error}
      rows={6}
      value={text}
      onChange={(e) => {
        const next = e.target.value
        setText(next)
        if (next.trim() === '') {
          setParseError(null)
          onChange(undefined)
          return
        }
        try {
          onChange(JSON.parse(next))
          setParseError(null)
        } catch (err) {
          setParseError(err instanceof Error ? err.message : 'Invalid JSON')
        }
      }}
    />
  )
}

const applyAjvErrors = (
  errors: ErrorObject[],
  setError: ReturnType<typeof useForm<FormValues>>['setError'],
) => {
  for (const err of errors) {
    // AJV instancePath: "/foo/bar" → RHF path: "data.foo.bar".
    // Раньше было "data" (root) для всех ошибок из-за {@code as 'data'} cast'а —
    // RHF складывал все ошибки в один ключ {@code formState.errors.data},
    // tab badge со счётчиком стабильно показывал 0/1, поле не подсвечивалось.
    const instancePath = err.instancePath.replace(/^\//, '').replace(/\//g, '.')
    // {@code required} keyword: instancePath пустой, имя missing field в
    // params.missingProperty (root уровень) или params.missingProperty
    // относительно instancePath (nested objects).
    let path = instancePath
    if (
      err.keyword === 'required' &&
      err.params &&
      typeof err.params === 'object' &&
      'missingProperty' in err.params
    ) {
      const missing = String((err.params as { missingProperty: string }).missingProperty)
      path = path ? `${path}.${missing}` : missing
    }
    const fieldPath = (path ? `data.${path}` : 'data') as Path<FormValues>
    setError(fieldPath, { type: 'ajv', message: err.message ?? 'invalid' })
  }
}

const firstFieldFromErrors = (errors: ErrorObject[]): string | null => {
  for (const err of errors) {
    const path = err.instancePath.replace(/^\//, '')
    // required keyword: instancePath relative to parent object, missing
    // property name в params.missingProperty. Combine, take top-level segment
    // — это то, что нужно для tab routing.
    if (
      err.keyword === 'required' &&
      err.params &&
      typeof err.params === 'object' &&
      'missingProperty' in err.params
    ) {
      const missing = String((err.params as { missingProperty: string }).missingProperty)
      const full = path ? `${path}/${missing}` : missing
      return full.split('/')[0]
    }
    if (path) return path.split('/')[0]
  }
  return null
}

const findTabForField = (
  fieldKey: string,
  buckets: Record<TabId, string[]>,
): TabId | null => {
  for (const id of ['identity', 'description', 'extra'] as TabId[]) {
    if (buckets[id].includes(fieldKey)) return id
  }
  return null
}

type ReferenceSelectFieldProps = {
  fieldKey: string
  schema: JsonSchema
  required: boolean
  control: ReturnType<typeof useForm<FormValues>>['control']
  defaultLocale: string
  label: string
  errorMsg?: string
}

/**
 * SingleSelect populated from a referenced dictionary's records.
 * Schema must have `x-references: "dict.field"`. Renders TextInput fallback
 * when the target dict is not accessible (scope-hide → 404/403 → empty list).
 *
 * Label format: `<businessKey> — <localized name>` if record has a name field,
 * otherwise just `<businessKey>`. Defaults to user's locale, falls back to
 * any available locale.
 */
const ReferenceSelectField = ({
  fieldKey,
  schema,
  required,
  control,
  defaultLocale,
  label,
  errorMsg,
}: ReferenceSelectFieldProps) => {
  const ref = schema['x-references'] ?? ''
  const [refDict, refField] = ref.split('.', 2)
  const { data: records, isLoading, isError } = useReferencedRecords(refDict)

  const options: SelectOption[] = useMemo(() => {
    if (!records || records.length === 0) return []
    return records
      .map((r) => buildOption(r, refField, defaultLocale))
      .filter((o): o is SelectOption => o !== null)
      .sort((a, b) => String(a.label ?? '').localeCompare(String(b.label ?? ''), 'ru'))
  }, [records, refField, defaultLocale])

  const hint = schema.description ?? `FK на ${ref}`

  // Fallback: target dict not accessible OR empty → free text input so user
  // can still enter the FK value manually (server-side ReferenceValidator
  // will validate on submit). Better than blocking edit entirely.
  const accessible = !isError && (isLoading || (records && records.length > 0))

  if (!accessible) {
    return (
      <Controller
        control={control}
        name={`data.${fieldKey}` as `data.${string}`}
        render={({ field }) => (
          <div>
            <FieldLabel required={required}>{label}</FieldLabel>
            <TextInput {...field} value={(field.value as string | undefined) ?? ''} />
            <FieldHint>{hint}</FieldHint>
            <FieldError>{errorMsg}</FieldError>
          </div>
        )}
      />
    )
  }

  // Native <select> вместо SingleSelect: для FK с длинным списком
  // (50+ статусов, типов КА) и tight modal'ом native browser dropdown
  // лучше — popup auto-positioning не клипается viewport'ом, native
  // scroll и type-ahead search работают OOTB. SingleSelect popup
  // через createPortal с position:fixed не делает auto-flip и уходит
  // за нижний край когда триггер близко к низу modal'а.
  //
  // Right adornment: spinner если идёт fetch options, иначе caret.
  // pointer-events-none — иконка не блокирует click по нативному
  // select (он принимает все клики, включая поверх стрелки).
  return (
    <Controller
      control={control}
      name={`data.${fieldKey}` as `data.${string}`}
      render={({ field }) => (
        <div>
          <FieldLabel required={required}>{label}</FieldLabel>
          <div className="relative">
            <select
              value={(field.value as string | undefined) ?? ''}
              onChange={(e) => field.onChange(e.target.value || undefined)}
              disabled={isLoading}
              aria-invalid={Boolean(errorMsg)}
              aria-busy={isLoading}
              // appearance-none убирает OS default стрелку.
              // py-2 (8px) match'ит TextInput высоту. pr-10 — место под адорнмент.
              className={[
                'w-full appearance-none border rounded-sm pl-3 pr-10 py-2 text-body font-display',
                'bg-white text-black transition-colors',
                'focus:outline-none focus:ring-1 focus:ring-accent focus:border-accent',
                errorMsg ? 'border-mars' : 'border-line hover:border-ink/40',
                isLoading ? 'cursor-wait text-mute' : 'cursor-pointer',
              ].join(' ')}
            >
              <option value="">{isLoading ? '…' : '—'}</option>
              {options.map((opt) => (
                <option key={opt.id} value={opt.id}>
                  {opt.label}
                </option>
              ))}
            </select>
            <div
              className="pointer-events-none absolute inset-y-0 right-3 flex items-center"
              aria-hidden="true"
            >
              {isLoading ? (
                <CircleNotchIcon size={16} weight="bold" className="text-accent animate-spin" />
              ) : (
                <CaretDownIcon size={16} weight="regular" className="text-mute/70" />
              )}
            </div>
          </div>
          <FieldHint>{hint}</FieldHint>
          <FieldError>{errorMsg}</FieldError>
        </div>
      )}
    />
  )
}

/**
 * Build a SelectOption from a record. Uses `record.data[refField]` as the
 * option id (the FK value). Label combines id with a localized name field
 * when available — `OPERATIONAL — Действующий` is much friendlier than
 * just `OPERATIONAL`.
 */
const buildOption = (
  record: FlattenedRecord,
  refField: string | undefined,
  defaultLocale: string,
): SelectOption | null => {
  const id = refField ? record.data?.[refField] : record.businessKey
  const idStr = id != null ? String(id) : record.businessKey
  if (!idStr) return null

  const nameField = record.data?.['name']
  const display = pickLocalized(nameField, defaultLocale)
  const label = display ? `${idStr} — ${display}` : idStr
  return { id: idStr, label }
}

const countErrorsPerTab = (
  errors: ReturnType<typeof useForm<FormValues>>['formState']['errors'],
  buckets: Record<TabId, string[]>,
): Record<TabId, number> => {
  const counts: Record<TabId, number> = { identity: 0, description: 0, extra: 0 }
  if (errors.businessKey) counts.identity += 1
  const dataErrors = errors.data as Record<string, unknown> | undefined
  if (!dataErrors) return counts
  for (const key of Object.keys(dataErrors)) {
    const tab = findTabForField(key, buckets)
    if (tab) counts[tab] += 1
  }
  return counts
}
