/**
 * Unified custom field types and pure helpers.
 *
 * Replaces: entityCustomFieldLogic.ts, customFieldUtils.ts, customFieldFormUtils.ts
 */

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

/** A custom field definition (from ModelType structure or CustomField entity) */
export interface CustomFieldDefinition {
  id: string | null
  name: string
  type: string
  required: boolean
  options: string[]
  defaultValue: string | null
  orderIndex: number
  machineContextOnly: boolean
}

/** A persisted custom field value (from CustomFieldValue entity via API) */
export interface CustomFieldValue {
  id: string
  value: string
  customField: CustomFieldDefinition
}

/** Merged definition + value for form display and editing */
export interface CustomFieldInput {
  customFieldId: string | null
  customFieldValueId: string | null
  name: string
  type: string
  required: boolean
  options: string[]
  defaultValue: string | null
  orderIndex: number
  machineContextOnly: boolean
  value: string
  readOnly?: boolean
  /** options joined by newline — used by category editor textareas (v-model) */
  optionsText?: string
}

// ---------------------------------------------------------------------------
// Normalization — accept any shape, return canonical types
// ---------------------------------------------------------------------------

const ALLOWED_TYPES = ['text', 'number', 'select', 'boolean', 'date'] as const

/**
 * Normalize any raw field definition object into a CustomFieldDefinition.
 * Handles both standard `{name, type}` and legacy `{key, value: {type}}` formats.
 */
export function normalizeDefinition(raw: any, fallbackIndex = 0): CustomFieldDefinition | null {
  if (!raw || typeof raw !== 'object') return null

  // Resolve name: standard → legacy key → label
  const name = (
    typeof raw.name === 'string' ? raw.name.trim()
    : typeof raw.key === 'string' ? raw.key.trim()
    : typeof raw.label === 'string' ? raw.label.trim()
    : ''
  )
  if (!name) return null

  // Resolve type: standard → nested in value → fallback
  const rawType = (
    typeof raw.type === 'string' ? raw.type
    : typeof raw.value?.type === 'string' ? raw.value.type
    : 'text'
  ).toLowerCase()
  const type = ALLOWED_TYPES.includes(rawType as any) ? rawType : 'text'

  // Resolve required
  const required = typeof raw.required === 'boolean' ? raw.required
    : typeof raw.value?.required === 'boolean' ? raw.value.required
    : false

  // Resolve options
  const optionSource = Array.isArray(raw.options) ? raw.options
    : Array.isArray(raw.value?.options) ? raw.value.options
    : []
  const options = optionSource
    .map((o: any) => typeof o === 'string' ? o.trim() : typeof o?.value === 'string' ? o.value.trim() : String(o ?? '').trim())
    .filter((o: string) => o.length > 0 && o !== '[object Object]')

  // Resolve defaultValue
  const dv = raw.defaultValue ?? raw.value?.defaultValue ?? null
  const defaultValue = dv !== null && dv !== undefined && dv !== '' ? String(dv) : null

  // Resolve orderIndex
  const orderIndex = typeof raw.orderIndex === 'number' ? raw.orderIndex : fallbackIndex

  // Resolve machineContextOnly
  const machineContextOnly = !!raw.machineContextOnly

  // Resolve id
  const id = typeof raw.id === 'string' ? raw.id
    : typeof raw.customFieldId === 'string' ? raw.customFieldId
    : null

  return { id, name, type, required, options, defaultValue, orderIndex, machineContextOnly }
}

/**
 * Normalize a raw value entry into a CustomFieldValue.
 * Accepts the API format: `{ id, value, customField: {...} }`
 */
export function normalizeValue(raw: any): CustomFieldValue | null {
  if (!raw || typeof raw !== 'object') return null
  const cf = raw.customField
  const definition = normalizeDefinition(cf)
  if (!definition) return null
  const id = typeof raw.id === 'string' ? raw.id : ''
  const value = raw.value !== null && raw.value !== undefined ? String(raw.value) : ''
  return { id, value, customField: definition }
}

/**
 * Normalize an array of raw definitions into CustomFieldDefinition[].
 */
export function normalizeDefinitions(raw: any): CustomFieldDefinition[] {
  if (!Array.isArray(raw)) return []
  return raw
    .map((item: any, i: number) => normalizeDefinition(item, i))
    .filter((d: any): d is CustomFieldDefinition => d !== null)
    .sort((a, b) => a.orderIndex - b.orderIndex)
}

/**
 * Normalize an array of raw values into CustomFieldValue[].
 */
export function normalizeValues(raw: any): CustomFieldValue[] {
  if (!Array.isArray(raw)) return []
  return raw
    .map((item: any) => normalizeValue(item))
    .filter((v: any): v is CustomFieldValue => v !== null)
}

// ---------------------------------------------------------------------------
// Merge — THE one merge function
// ---------------------------------------------------------------------------

/**
 * Merge definitions from a ModelType with persisted values from an entity.
 * Returns a CustomFieldInput[] ready for form display.
 *
 * Match strategy: by customField.id first, then by name (case-sensitive).
 * When no value exists for a definition, uses defaultValue as initial value.
 */
export function mergeDefinitionsWithValues(
  rawDefinitions: any,
  rawValues: any,
): CustomFieldInput[] {
  const definitions = normalizeDefinitions(rawDefinitions)
  const values = normalizeValues(rawValues)

  // Build lookup maps for values
  const valueById = new Map<string, CustomFieldValue>()
  const valueByName = new Map<string, CustomFieldValue>()
  for (const v of values) {
    if (v.customField.id) valueById.set(v.customField.id, v)
    valueByName.set(v.customField.name, v)
  }

  const matchedValueIds = new Set<string>()
  const matchedNames = new Set<string>()

  // 1. Map definitions to inputs, matching values
  const result: CustomFieldInput[] = definitions.map((def) => {
    const matched = (def.id ? valueById.get(def.id) : undefined) ?? valueByName.get(def.name)

    const optionsText = def.options.length ? def.options.join('\n') : undefined

    if (matched) {
      if (matched.id) matchedValueIds.add(matched.id)
      matchedNames.add(def.name)
      return {
        customFieldId: def.id,
        customFieldValueId: matched.id || null,
        name: def.name,
        type: def.type,
        required: def.required,
        options: def.options,
        defaultValue: def.defaultValue,
        orderIndex: def.orderIndex,
        machineContextOnly: def.machineContextOnly,
        value: matched.value,
        optionsText,
      }
    }

    // No value found — use defaultValue
    return {
      customFieldId: def.id,
      customFieldValueId: null,
      name: def.name,
      type: def.type,
      required: def.required,
      options: def.options,
      defaultValue: def.defaultValue,
      orderIndex: def.orderIndex,
      machineContextOnly: def.machineContextOnly,
      value: def.defaultValue ?? '',
      optionsText,
    }
  })

  // 2. Add orphan values (have a value but no matching definition)
  for (const v of values) {
    if (matchedValueIds.has(v.id)) continue
    if (matchedNames.has(v.customField.name)) continue

    const orphanOptionsText = v.customField.options.length ? v.customField.options.join('\n') : undefined
    result.push({
      customFieldId: v.customField.id,
      customFieldValueId: v.id || null,
      name: v.customField.name,
      type: v.customField.type,
      required: v.customField.required,
      options: v.customField.options,
      defaultValue: v.customField.defaultValue,
      orderIndex: v.customField.orderIndex,
      machineContextOnly: v.customField.machineContextOnly,
      value: v.value,
      optionsText: orphanOptionsText,
    })
  }

  return result.sort((a, b) => a.orderIndex - b.orderIndex)
}

// ---------------------------------------------------------------------------
// Filter & sort
// ---------------------------------------------------------------------------

/** Filter fields by context: standalone hides machineContextOnly, machine shows only machineContextOnly */
export function filterByContext(
  fields: CustomFieldInput[],
  context: 'standalone' | 'machine',
): CustomFieldInput[] {
  if (context === 'machine') return fields.filter((f) => f.machineContextOnly)
  return fields.filter((f) => !f.machineContextOnly)
}

/** Sort fields by orderIndex */
export function sortByOrder(fields: CustomFieldInput[]): CustomFieldInput[] {
  return [...fields].sort((a, b) => a.orderIndex - b.orderIndex)
}

// ---------------------------------------------------------------------------
// Display helpers
// ---------------------------------------------------------------------------

/** Format a field value for display (e.g. boolean → Oui/Non) */
export function formatValueForDisplay(field: CustomFieldInput): string {
  const raw = field.value ?? ''
  if (field.type === 'boolean') {
    const normalized = String(raw).toLowerCase()
    if (normalized === 'true' || normalized === '1') return 'Oui'
    if (normalized === 'false' || normalized === '0') return 'Non'
  }
  return raw || 'Non défini'
}

/** Whether a field has a displayable value (readOnly fields always display) */
export function hasDisplayableValue(field: CustomFieldInput): boolean {
  if (field.readOnly) return true
  if (field.type === 'boolean') return field.value !== undefined && field.value !== null && field.value !== ''
  return typeof field.value === 'string' && field.value.trim().length > 0
}

/** Stable key for v-for rendering */
export function fieldKey(field: CustomFieldInput, index: number): string {
  return field.customFieldValueId || field.customFieldId || `${field.name}-${index}`
}

// ---------------------------------------------------------------------------
// Persistence helpers
// ---------------------------------------------------------------------------

/** Whether a field should be persisted (non-empty value) */
export function shouldPersist(field: CustomFieldInput): boolean {
  if (field.type === 'boolean') return field.value === 'true' || field.value === 'false'
  if (typeof field.value === 'number') return !Number.isNaN(field.value)
  return typeof field.value === 'string' && field.value.trim() !== ''
}

/** Format value for save (trim, boolean coercion) */
export function formatValueForSave(field: CustomFieldInput): string {
  if (field.type === 'boolean') return field.value === 'true' ? 'true' : 'false'
  if (typeof field.value === 'number') return String(field.value)
  return typeof field.value === 'string' ? field.value.trim() : ''
}

/** Check if all required fields are filled */
export function requiredFieldsFilled(fields: CustomFieldInput[]): boolean {
  return fields.every((field) => {
    if (!field.required) return true
    return shouldPersist(field)
  })
}
