<template>
  <div class="space-y-4">
    <header class="flex items-center justify-between">
      <h3 class="text-sm font-semibold">
        Champs personnalisés
      </h3>
      <button type="button" class="btn btn-outline btn-xs" @click="addField">
        <IconLucidePlus class="w-3 h-3 mr-2" aria-hidden="true" />
        Ajouter
      </button>
    </header>

    <p v-if="!fields.length" class="text-xs text-gray-500">
      Aucun champ personnalisé n'a encore été défini.
    </p>

    <ul v-else class="space-y-2" role="list">
      <li
        v-for="(field, index) in fields"
        :key="field.uid"
        class="border border-base-200 rounded-md p-3 space-y-2 bg-base-100 transition-colors"
        :class="reorderClass(index)"
        draggable="true"
        @dragstart="onDragStart(index, $event)"
        @dragenter="onDragEnter(index)"
        @dragover.prevent="onDragEnter(index)"
        @drop.prevent="onDrop(index)"
        @dragend="onDragEnd"
      >
        <div class="flex items-start gap-3">
          <button
            type="button"
            class="btn btn-ghost btn-xs btn-square cursor-grab active:cursor-grabbing mt-1"
            title="Réordonner"
            draggable="false"
          >
            <IconLucideGripVertical class="w-4 h-4" aria-hidden="true" />
          </button>

          <div class="flex-1 space-y-2">
            <div class="grid grid-cols-1 md:grid-cols-2 gap-2">
              <input
                v-model="field.name"
                type="text"
                class="input input-bordered input-xs"
                placeholder="Nom du champ"
              >
              <select v-model="field.type" class="select select-bordered select-xs">
                <option value="text">
                  Texte
                </option>
                <option value="number">
                  Nombre
                </option>
                <option value="select">
                  Liste
                </option>
                <option value="boolean">
                  Oui/Non
                </option>
                <option value="date">
                  Date
                </option>
              </select>
            </div>

            <div class="flex items-center gap-2 text-xs">
              <input v-model="field.required" type="checkbox" class="checkbox checkbox-xs">
              Obligatoire
            </div>

            <textarea
              v-if="field.type === 'select'"
              v-model="field.optionsText"
              class="textarea textarea-bordered textarea-xs h-20"
              placeholder="Option 1&#10;Option 2"
            />
          </div>

          <button
            type="button"
            class="btn btn-error btn-xs btn-square"
            @click="removeField(index)"
          >
            <IconLucideTrash class="w-4 h-4" aria-hidden="true" />
          </button>
        </div>
      </li>
    </ul>
  </div>
</template>

<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import IconLucideGripVertical from '~icons/lucide/grip-vertical'
import IconLucidePlus from '~icons/lucide/plus'
import IconLucideTrash from '~icons/lucide/trash'
import type {
  PieceModelCustomField,
  PieceModelCustomFieldType,
  PieceModelStructure,
  PieceModelStructureEditorField,
} from '~/shared/types/inventory'
import { normalizePieceStructureForSave } from '~/shared/modelUtils'

defineOptions({ name: 'PieceModelStructureEditor' })

type EditorField = PieceModelStructureEditorField & { uid: string }

const props = defineProps<{
  modelValue?: PieceModelStructure | null
}>()

const emit = defineEmits<{
  (event: 'update:modelValue', value: PieceModelStructure): void
}>()

const ensureArray = <T>(value: T[] | null | undefined): T[] => (Array.isArray(value) ? value : [])

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

const safeClone = <T>(value: T, fallback: T): T => {
  try {
    return JSON.parse(JSON.stringify(value ?? fallback)) as T
  } catch {
    return JSON.parse(JSON.stringify(fallback)) as T
  }
}

const extractRest = (structure?: PieceModelStructure | null): Record<string, unknown> => {
  if (!structure || typeof structure !== 'object') {
    return {}
  }
  const entries = Object.entries(structure).filter(([key]) => key !== 'customFields')
  return safeClone(Object.fromEntries(entries), {})
}

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

const toEditorField = (
  input: Partial<PieceModelStructureEditorField> | null | undefined,
  index: number,
): EditorField => {
  const baseType = typeof input?.type === 'string' && input.type ? input.type : 'text'
  const optionsText = normalizeLineEndings(
    typeof input?.optionsText === 'string'
      ? input.optionsText
      : Array.isArray(input?.options)
        ? input.options.join('\n')
        : '',
  )

  return {
    uid: createUid(),
    name: typeof input?.name === 'string' ? input.name : '',
    type: baseType as PieceModelCustomFieldType,
    required: Boolean(input?.required),
    optionsText,
    orderIndex: typeof input?.orderIndex === 'number' ? input.orderIndex : index,
  }
}

const hydrateFields = (structure?: PieceModelStructure | null): EditorField[] => {
  const source = ensureArray(structure?.customFields)
  return source
    .map((field, index) => toEditorField(field, index))
    .sort((a, b) => (a.orderIndex ?? 0) - (b.orderIndex ?? 0))
    .map((field, index) => ({ ...field, orderIndex: index }))
}

const fields = ref<EditorField[]>(hydrateFields(props.modelValue))
const restState = ref<Record<string, unknown>>(extractRest(props.modelValue))

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

const buildPayload = (
  fieldsSource: EditorField[],
  restSource: Record<string, unknown>,
): PieceModelStructure => {
  const normalizedFields = fieldsSource
    .map<PieceModelCustomField | null>((field, index) => {
      const name = field.name.trim()
      if (!name) {
        return null
      }

      const type = (field.type || 'text') as PieceModelCustomFieldType
      const required = Boolean(field.required)
      const payload: PieceModelCustomField = {
        name,
        type,
        required,
        orderIndex: index,
      }

      if (type === 'select') {
        const options = normalizeLineEndings(field.optionsText)
          .split('\n')
          .map((option) => option.trim())
          .filter((option) => option.length > 0)
        if (options.length > 0) {
          payload.options = options
        }
      }

      return payload
    })
    .filter((field): field is PieceModelCustomField => Boolean(field))

  const draft: PieceModelStructure = {
    ...safeClone(restSource, {}),
    customFields: normalizedFields,
  }

  return normalizePieceStructureForSave(draft)
}

const serializeStructure = (structure?: PieceModelStructure | null): string => {
  return JSON.stringify(normalizePieceStructureForSave(structure ?? { customFields: [] }))
}

let lastEmitted = serializeStructure(props.modelValue)

const emitUpdate = () => {
  const payload = buildPayload(fields.value, restState.value)
  const serialized = JSON.stringify(payload)
  if (serialized !== lastEmitted) {
    lastEmitted = serialized
    emit('update:modelValue', payload)
  }
}

watch(fields, emitUpdate, { deep: true })

watch(
  () => props.modelValue,
  (value) => {
    const incomingSerialized = serializeStructure(value)
    if (incomingSerialized === lastEmitted) {
      return
    }
    restState.value = extractRest(value)
    fields.value = hydrateFields(value)
    lastEmitted = incomingSerialized
  },
  { deep: true },
)

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

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

const reorderFields = (from: number, to: number) => {
  if (from === to) {
    resetDragState()
    return
  }

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

  const [moved] = list.splice(from, 1)
  list.splice(to, 0, moved)
  fields.value = applyOrderIndex(list)
  resetDragState()
}

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) => {
  if (dragState.draggingIndex === null) {
    resetDragState()
    return
  }
  reorderFields(dragState.draggingIndex, index)
}

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

const reorderClass = (index: number) => {
  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 ''
}

const createEmptyField = (orderIndex: number): EditorField => ({
  uid: createUid(),
  name: '',
  type: 'text',
  required: false,
  optionsText: '',
  orderIndex,
})

const addField = () => {
  const next = fields.value.slice()
  next.push(createEmptyField(next.length))
  fields.value = applyOrderIndex(next)
}

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