/**
 * Unified reactive custom field management composable.
 *
 * Replaces: useEntityCustomFields.ts, custom field parts of useMachineDetailCustomFields.ts,
 * and inline custom field logic in useComponentEdit/useComponentCreate/usePieceEdit.
 *
 * DESIGN NOTE: Uses an internal mutable `ref` (not a `computed`) so that
 * save operations can update `customFieldValueId` in place without being
 * overwritten on the next reactivity cycle. Call `refresh()` to re-merge
 * from the source definitions + values (e.g. after fetching fresh data).
 */

import { ref, watch, computed, type MaybeRef, toValue } from 'vue'
import { useCustomFields } from '~/composables/useCustomFields'
import { useToast } from '~/composables/useToast'
import {
  mergeDefinitionsWithValues,
  filterByContext,
  formatValueForSave,
  shouldPersist,
  requiredFieldsFilled,
  type CustomFieldDefinition,
  type CustomFieldValue,
  type CustomFieldInput,
} from '~/shared/utils/customFields'

export type { CustomFieldDefinition, CustomFieldValue, CustomFieldInput }

export type CustomFieldEntityType =
  | 'machine'
  | 'composant'
  | 'piece'
  | 'product'
  | 'machineComponentLink'
  | 'machinePieceLink'

export interface UseCustomFieldInputsOptions {
  /** Custom field definitions (from ModelType structure or machine.customFields) */
  definitions: MaybeRef<any[]>
  /** Persisted custom field values (from entity.customFieldValues or link.contextCustomFieldValues) */
  values: MaybeRef<any[]>
  /** Entity type for API upsert calls */
  entityType: CustomFieldEntityType
  /** Entity ID for API upsert calls */
  entityId: MaybeRef<string | null>
  /** Filter context: 'standalone' hides machineContextOnly, 'machine' shows only machineContextOnly */
  context?: 'standalone' | 'machine'
  /** Optional callback to update the source values array after a save (keeps parent reactive state in sync) */
  onValueCreated?: (newValue: { id: string; value: string; customField: any }) => void
}

export function useCustomFieldInputs(options: UseCustomFieldInputsOptions) {
  const { entityType, context } = options
  const {
    updateCustomFieldValue: updateApi,
    upsertCustomFieldValue,
  } = useCustomFields()
  const { showSuccess, showError } = useToast()

  // Internal mutable state — NOT a computed, so save can mutate in place
  const _allFields = ref<CustomFieldInput[]>([])

  // Re-merge from source definitions + values
  const refresh = () => {
    const defs = toValue(options.definitions)
    const vals = toValue(options.values)
    _allFields.value = mergeDefinitionsWithValues(defs, vals)
  }

  // Auto-refresh when reactive sources change
  watch(
    () => [toValue(options.definitions), toValue(options.values)],
    () => refresh(),
    { immediate: true, deep: true },
  )

  // Filtered by context (standalone vs machine)
  const fields = computed<CustomFieldInput[]>(() => {
    if (!context) return _allFields.value
    return filterByContext(_allFields.value, context)
  })

  // Validation
  const requiredFilled = computed(() => requiredFieldsFilled(fields.value))

  // Build metadata for upsert when no customFieldId is available (legacy fallback)
  const _buildMetadata = (field: CustomFieldInput) => ({
    customFieldName: field.name,
    customFieldType: field.type,
    customFieldRequired: field.required,
    customFieldOptions: field.options,
  })

  // Update a single field value
  const update = async (field: CustomFieldInput): Promise<boolean> => {
    const id = toValue(options.entityId)
    if (!id) {
      showError(`Impossible de sauvegarder le champ "${field.name}"`)
      return false
    }

    const value = formatValueForSave(field)

    // Update existing value
    if (field.customFieldValueId) {
      const result: any = await updateApi(field.customFieldValueId, { value })
      if (result.success) {
        showSuccess(`Champ "${field.name}" mis à jour`)
        return true
      }
      showError(`Erreur lors de la mise à jour du champ "${field.name}"`)
      return false
    }

    // Create new value via upsert — with metadata fallback when no ID
    const metadata = field.customFieldId ? undefined : _buildMetadata(field)
    const result: any = await upsertCustomFieldValue(
      field.customFieldId,
      entityType,
      id,
      value,
      metadata,
    )

    if (result.success) {
      // Mutate in place (safe — _allFields is a ref, not computed)
      if (result.data?.id) {
        field.customFieldValueId = result.data.id
      }
      if (result.data?.customField?.id) {
        field.customFieldId = result.data.customField.id
      }
      // Notify parent to update its reactive source
      if (options.onValueCreated && result.data) {
        options.onValueCreated(result.data)
      }
      showSuccess(`Champ "${field.name}" enregistré`)
      return true
    }

    showError(`Erreur lors de l'enregistrement du champ "${field.name}"`)
    return false
  }

  // Save all fields that have values
  const saveAll = async (): Promise<string[]> => {
    const id = toValue(options.entityId)
    if (!id) return ['(entity ID missing)']

    const failed: string[] = []

    for (const field of fields.value) {
      if (!shouldPersist(field)) continue

      const value = formatValueForSave(field)

      if (field.customFieldValueId) {
        const result: any = await updateApi(field.customFieldValueId, { value })
        if (!result.success) failed.push(field.name)
        continue
      }

      // Upsert with metadata fallback when no customFieldId
      const metadata = field.customFieldId ? undefined : _buildMetadata(field)
      const result: any = await upsertCustomFieldValue(
        field.customFieldId,
        entityType,
        id,
        value,
        metadata,
      )

      if (result.success) {
        if (result.data?.id) {
          field.customFieldValueId = result.data.id
        }
        if (result.data?.customField?.id) {
          field.customFieldId = result.data.customField.id
        }
        if (options.onValueCreated && result.data) {
          options.onValueCreated(result.data)
        }
      } else {
        failed.push(field.name)
      }
    }

    return failed
  }

  return {
    /** All merged fields filtered by context */
    fields,
    /** All merged fields (unfiltered) */
    allFields: _allFields,
    /** Whether all required fields have values */
    requiredFilled,
    /** Update a single field value via API */
    update,
    /** Save all fields with values, returns list of failed field names */
    saveAll,
    /** Re-merge from source definitions + values (call after fetching fresh data) */
    refresh,
  }
}
