import { reactive, ref } from 'vue'
import { useApi } from '~/composables/useApi'
import { useCustomFieldNameSuggestions } from '~/composables/useCustomFieldNameSuggestions'
import { useToast } from '~/composables/useToast'

// --- Types ---

export type MachineFieldType = 'text' | 'number' | 'select' | 'boolean' | 'date'

export interface MachineCustomFieldEditorField {
  uid: string
  serverId?: string
  name: string
  type: MachineFieldType
  required: boolean
  optionsText: string
  orderIndex: number
}

interface InitialDef {
  id: string
  name: string
  type: string
  required: boolean
  options?: string[]
  orderIndex: number
  defaultValue?: unknown
}

interface Deps {
  machineId: string
  initialDefs: InitialDef[]
  onSaved: () => void | Promise<void>
}

// --- Helpers ---

let uidCounter = 0
const createUid = (): string => {
  if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
    return crypto.randomUUID()
  }
  uidCounter += 1
  return `mcf-${Date.now().toString(36)}-${uidCounter}`
}

const normalizeLineEndings = (value: string): string =>
  value.replace(/\r\n/g, '\n').replace(/\r/g, '\n')

const toEditorField = (def: InitialDef, index: number): MachineCustomFieldEditorField => ({
  uid: createUid(),
  serverId: def.id,
  name: def.name || '',
  type: (def.type || 'text') as MachineFieldType,
  required: Boolean(def.required),
  optionsText: normalizeLineEndings(
    Array.isArray(def.options) ? def.options.join('\n') : '',
  ),
  orderIndex: typeof def.orderIndex === 'number' ? def.orderIndex : index,
})

const hydrateFields = (defs: InitialDef[]): MachineCustomFieldEditorField[] =>
  defs
    .map((def, index) => toEditorField(def, index))
    .sort((a, b) => a.orderIndex - b.orderIndex)
    .map((field, index) => ({ ...field, orderIndex: index }))

const buildSnapshot = (defs: InitialDef[]): Map<string, InitialDef> => {
  const map = new Map<string, InitialDef>()
  for (const def of defs) {
    map.set(def.id, def)
  }
  return map
}

const applyOrderIndex = (
  list: MachineCustomFieldEditorField[],
): MachineCustomFieldEditorField[] =>
  list.map((field, index) => ({ ...field, orderIndex: index }))

const parseOptions = (optionsText: string): string[] =>
  normalizeLineEndings(optionsText)
    .split('\n')
    .map(o => o.trim())
    .filter(o => o.length > 0)

// --- Composable ---

export function useMachineCustomFieldDefs(deps: Deps) {
  const { apiCall } = useApi()
  const { showSuccess, showError } = useToast()
  const { invalidate: invalidateCustomFieldNames } = useCustomFieldNameSuggestions()

  // --- State ---

  const fields = ref<MachineCustomFieldEditorField[]>(hydrateFields(deps.initialDefs))
  const initialSnapshot = ref<Map<string, InitialDef>>(buildSnapshot(deps.initialDefs))
  const saving = ref(false)

  // --- CRUD ---

  const addField = () => {
    const next = fields.value.slice()
    next.push({
      uid: createUid(),
      name: '',
      type: 'text',
      required: false,
      optionsText: '',
      orderIndex: next.length,
    })
    fields.value = applyOrderIndex(next)
  }

  const removeField = (index: number) => {
    const next = fields.value.filter((_, i) => i !== index)
    fields.value = applyOrderIndex(next)
  }

  // --- Drag & drop ---

  const dragState = reactive({
    draggingIndex: null as number | null,
    dropTargetIndex: null as number | null,
  })

  const resetDragState = () => {
    dragState.draggingIndex = null
    dragState.dropTargetIndex = null
  }

  const onDragStart = (index: number, event: DragEvent) => {
    dragState.draggingIndex = index
    dragState.dropTargetIndex = index
    if (event.dataTransfer) {
      event.dataTransfer.effectAllowed = 'move'
    }
  }

  const onDragEnter = (index: number) => {
    if (dragState.draggingIndex === null) return
    dragState.dropTargetIndex = index
  }

  const onDrop = (index: number) => {
    const from = dragState.draggingIndex
    if (from === null) {
      resetDragState()
      return
    }

    if (from === index) {
      resetDragState()
      return
    }

    const list = fields.value.slice()
    if (from < 0 || index < 0 || from >= list.length || index >= list.length) {
      resetDragState()
      return
    }

    const [moved] = list.splice(from, 1)
    if (!moved) {
      resetDragState()
      return
    }
    list.splice(index, 0, moved)
    fields.value = applyOrderIndex(list)
    resetDragState()
  }

  const onDragEnd = () => {
    resetDragState()
  }

  const reorderClass = (index: number): string => {
    if (dragState.draggingIndex === index) {
      return 'border-dashed border-primary bg-primary/5'
    }
    if (
      dragState.draggingIndex !== null
      && dragState.dropTargetIndex === index
      && dragState.draggingIndex !== index
    ) {
      return 'border-primary border-dashed bg-primary/10'
    }
    return ''
  }

  // --- Save ---

  const saveDefinitions = async () => {
    if (saving.value) return

    // Validate: remove empty-name fields before saving
    const emptyNameFields = fields.value.filter(f => !f.name.trim() && !f.serverId)
    if (emptyNameFields.length > 0) {
      fields.value = applyOrderIndex(fields.value.filter(f => f.name.trim() || f.serverId))
    }

    saving.value = true

    try {
      const snapshot = initialSnapshot.value
      const currentServerIds = new Set(
        fields.value.filter(f => f.serverId).map(f => f.serverId!),
      )

      // DELETE removed fields
      const deletedIds = [...snapshot.keys()].filter(id => !currentServerIds.has(id))
      for (const id of deletedIds) {
        const result = await apiCall(`/custom_fields/${id}`, { method: 'DELETE' })
        if (!result.success) {
          showError('Erreur lors de la suppression d\'un champ personnalisé')
          await deps.onSaved()
          return
        }
      }

      let hasNewFields = false

      for (const field of fields.value) {
        const name = field.name.trim()
        if (!name) continue

        const options = field.type === 'select' ? parseOptions(field.optionsText) : []

        if (!field.serverId) {
          // POST new field
          hasNewFields = true
          const body: Record<string, unknown> = {
            name,
            type: field.type,
            required: field.required,
            options,
            orderIndex: field.orderIndex,
            machine: `/api/machines/${deps.machineId}`,
          }

          const result = await apiCall('/custom_fields', {
            method: 'POST',
            headers: { 'Content-Type': 'application/ld+json' },
            body: JSON.stringify(body),
          })
          if (!result.success) {
            showError('Erreur lors de la création d\'un champ personnalisé')
            await deps.onSaved()
            return
          }
        } else {
          // PATCH modified field
          const original = snapshot.get(field.serverId)
          const originalOptions = Array.isArray(original?.options)
            ? original.options.join('\n')
            : ''
          const currentOptions = field.type === 'select' ? field.optionsText : ''

          const changed
            = original?.name !== name
            || original?.type !== field.type
            || original?.required !== field.required
            || normalizeLineEndings(originalOptions) !== normalizeLineEndings(currentOptions)
            || original?.orderIndex !== field.orderIndex

          if (changed) {
            const body: Record<string, unknown> = {
              name,
              type: field.type,
              required: field.required,
              options,
              orderIndex: field.orderIndex,
            }

            const result = await apiCall(`/custom_fields/${field.serverId}`, {
              method: 'PATCH',
              headers: { 'Content-Type': 'application/merge-patch+json' },
              body: JSON.stringify(body),
            })
            if (!result.success) {
              showError('Erreur lors de la mise à jour d\'un champ personnalisé')
              await deps.onSaved()
              return
            }
          }
        }
      }

      // Initialize missing custom field values if new fields were created
      if (hasNewFields) {
        await apiCall(`/machines/${deps.machineId}/add-custom-fields`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/ld+json' },
          body: JSON.stringify({}),
        })
      }

      showSuccess('Champs personnalisés sauvegardés avec succès')
      invalidateCustomFieldNames()
      await deps.onSaved()
    } catch {
      showError('Erreur inattendue lors de la sauvegarde des champs personnalisés')
      await deps.onSaved()
    } finally {
      saving.value = false
    }
  }

  // --- Reinit ---

  const reinit = (newDefs: InitialDef[]) => {
    fields.value = hydrateFields(newDefs)
    initialSnapshot.value = buildSnapshot(newDefs)
  }

  return {
    fields,
    saving,
    dragState,
    addField,
    removeField,
    onDragStart,
    onDragEnter,
    onDrop,
    onDragEnd,
    reorderClass,
    saveDefinitions,
    reinit,
  }
}
