import { computed, onMounted, reactive, ref, watch } from 'vue'
import type {
  PieceModelCustomField,
  PieceModelCustomFieldType,
  PieceModelProduct,
  PieceModelStructure,
  PieceModelStructureEditorField,
} from '~/shared/types/inventory'
import { normalizePieceStructureForSave } from '~/shared/modelUtils'
import { useProductTypes } from '~/composables/useProductTypes'

export type EditorField = PieceModelStructureEditorField & { uid: string }
export type EditorProduct = {
  uid: string
  typeProductId: string
  typeProductLabel: string
  familyCode: string
}

interface Deps {
  props: {
    modelValue?: PieceModelStructure | null
  }
  emit: (event: 'update:modelValue', value: PieceModelStructure) => void
}

// --- Pure helpers ---

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' && key !== 'products',
  )
  return safeClone(Object.fromEntries(entries), {})
}

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

// --- Hydration ---

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('field'),
    name: typeof input?.name === 'string' ? input.name : '',
    type: baseType as PieceModelCustomFieldType,
    required: Boolean(input?.required),
    optionsText,
    defaultValue:
      input?.defaultValue !== undefined && input.defaultValue !== null && input.defaultValue !== ''
        ? String(input.defaultValue)
        : null,
    ...(typeof input?.id === 'string' && input.id ? { id: input.id } : {}),
    ...(typeof input?.customFieldId === 'string' && input.customFieldId ? { customFieldId: input.customFieldId } : {}),
    orderIndex: typeof input?.orderIndex === 'number' ? input.orderIndex : index,
    machineContextOnly: Boolean(input?.machineContextOnly),
  }
}

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 toEditorProduct = (
  input: Partial<PieceModelProduct> | null | undefined,
): EditorProduct => ({
  uid: createUid('product'),
  typeProductId: typeof input?.typeProductId === 'string' ? input.typeProductId : '',
  typeProductLabel:
    typeof input?.typeProductLabel === 'string' ? input.typeProductLabel : '',
  familyCode: typeof input?.familyCode === 'string' ? input.familyCode : '',
})

const hydrateProducts = (structure?: PieceModelStructure | null): EditorProduct[] => {
  const source = Array.isArray(structure?.products) ? structure?.products : []
  return source.map(product => toEditorProduct(product))
}

// --- Payload ---

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

const normalizeProductEntry = (product: EditorProduct): PieceModelProduct | null => {
  const typeProductId = typeof product.typeProductId === 'string' ? product.typeProductId.trim() : ''
  const familyCode = typeof product.familyCode === 'string' ? product.familyCode.trim() : ''

  if (!typeProductId && !familyCode) {
    return null
  }

  const payload: PieceModelProduct = {}
  if (typeProductId) {
    payload.typeProductId = typeProductId
  }
  if (familyCode) {
    payload.familyCode = familyCode
  }
  if (product.typeProductLabel) {
    payload.typeProductLabel = product.typeProductLabel
  }
  return payload
}

const buildPayload = (
  fieldsSource: EditorField[],
  productsSource: EditorProduct[],
  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,
        machineContextOnly: Boolean(field.machineContextOnly),
      }

      if (field.id) {
        payload.id = field.id
      }
      if (field.customFieldId) {
        payload.customFieldId = field.customFieldId
      }
      if (field.defaultValue !== undefined && field.defaultValue !== null && field.defaultValue !== '') {
        payload.defaultValue = String(field.defaultValue)
      }

      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 normalizedProducts = productsSource
    .map(product => normalizeProductEntry(product))
    .filter((product): product is PieceModelProduct => Boolean(product))

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

  return normalizePieceStructureForSave(draft)
}

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

// --- Composable ---

export function usePieceStructureEditorLogic(deps: Deps) {
  const { props, emit } = deps
  const { productTypes, loadProductTypes } = useProductTypes()

  // --- State ---

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


  // --- Product types ---

  const productTypeOptions = computed(() => productTypes.value ?? [])

  const productTypeMap = computed(() => {
    const map = new Map<string, any>()
    productTypeOptions.value.forEach((type: any) => {
      if (type?.id) {
        map.set(type.id, type)
      }
    })
    return map
  })

  const formatProductTypeOption = (type: any) => {
    if (!type) {
      return ''
    }
    const parts: string[] = []
    if (type.code) {
      parts.push(type.code)
    }
    if (type.name) {
      parts.push(type.name)
    }
    return parts.length ? parts.join(' • ') : type.id || ''
  }

  const updateProductTypeMetadata = (product: EditorProduct) => {
    const option = product.typeProductId
      ? productTypeMap.value.get(product.typeProductId)
      : null
    product.typeProductLabel = option?.name ?? ''
  }

  const handleProductTypeSelect = (product: EditorProduct) => {
    const option = product.typeProductId
      ? productTypeMap.value.get(product.typeProductId)
      : null
    product.typeProductLabel = option?.name ?? ''
    if (option?.code) {
      product.familyCode = option.code
    }
  }

  // --- CRUD ---

  const createEmptyProduct = (): EditorProduct => ({
    uid: createUid('product'),
    typeProductId: '',
    typeProductLabel: '',
    familyCode: '',
  })

  const addProduct = () => {
    products.value.push(createEmptyProduct())
  }

  const removeProduct = (index: number) => {
    products.value = products.value.filter((_, idx) => idx !== index)
  }

  const createEmptyField = (orderIndex: number): EditorField => ({
    uid: createUid('field'),
    name: '',
    type: 'text',
    required: false,
    optionsText: '',
    machineContextOnly: false,
    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)
  }

  // --- Drag & drop ---

  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)
    if (!moved) {
      resetDragState()
      return
    }
    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 ''
  }

  // --- Emit ---

  let lastEmitted = serializeStructure(props.modelValue)

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

  // --- Watchers ---

  watch(fields, emitUpdate, { deep: true })
  watch(products, emitUpdate, { deep: true })
  watch(productTypeOptions, () => {
    products.value.forEach(product => updateProductTypeMetadata(product))
  })

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

  // --- Lifecycle ---

  onMounted(async () => {
    if (!productTypeOptions.value.length) {
      await loadProductTypes()
    }
    products.value.forEach(product => updateProductTypeMetadata(product))
  })

  return {
    fields,
    products,
    productTypeOptions,
    formatProductTypeOption,
    handleProductTypeSelect,
    addProduct,
    removeProduct,
    addField,
    removeField,
    reorderClass,
    onDragStart,
    onDragEnter,
    onDrop,
    onDragEnd,
  }
}
