<template>
  <div class="space-y-6">
    <StructureNodeEditor
      :node="localStructure"
      :depth="0"
      :component-types="availableComponentTypes"
      :piece-types="availablePieceTypes"
      :product-types="availableProductTypes"
      :lock-type="lockRootType"
      :locked-type-label="displayedRootTypeLabel"
      :allow-subcomponents="allowSubcomponents"
      :max-subcomponent-depth="maxSubcomponentDepth"
      is-root
    />
  </div>
</template>

<script setup lang="ts">
import { reactive, watch, computed, onMounted, ref } from 'vue'
import StructureNodeEditor from '~/components/StructureNodeEditor.vue'
import {
  defaultStructure,
  hydrateStructureForEditor,
  cloneStructure,
} from '~/shared/modelUtils'
import { usePieceTypes } from '~/composables/usePieceTypes'
import { useComponentTypes } from '~/composables/useComponentTypes'
import { useProductTypes } from '~/composables/useProductTypes'
import type { ComponentModelStructure } from '~/shared/types/inventory'

defineOptions({ name: 'ComponentModelStructureEditor' })

const props = defineProps({
  modelValue: {
    type: Object,
    default: () => defaultStructure(),
  },
  rootTypeId: {
    type: String,
    default: '',
  },
  rootTypeLabel: {
    type: String,
    default: '',
  },
  lockRootType: {
    type: Boolean,
    default: false,
  },
  allowSubcomponents: {
    type: Boolean,
    default: true,
  },
  maxSubcomponentDepth: {
    type: Number,
    default: Infinity,
  },
})

const emit = defineEmits(['update:modelValue'])

const localStructure = reactive<ComponentModelStructure>(hydrateStructureForEditor(props.modelValue))
const previousLockedLabel = ref(props.rootTypeLabel || '')

const { pieceTypes, loadPieceTypes } = usePieceTypes()
const { componentTypes, loadComponentTypes } = useComponentTypes()
const { productTypes, loadProductTypes } = useProductTypes()

const availablePieceTypes = computed(() => pieceTypes.value ?? [])
const availableComponentTypes = computed(() => componentTypes.value ?? [])
const availableProductTypes = computed(() => productTypes.value ?? [])
const allowSubcomponents = computed(() => props.allowSubcomponents !== false)
const maxSubcomponentDepth = computed(() =>
  typeof props.maxSubcomponentDepth === 'number' ? props.maxSubcomponentDepth : Infinity,
)

const fallbackRootTypeLabel = computed(() => {
  if (!props.rootTypeId) {
    return ''
  }
  const match = availableComponentTypes.value.find((type) => type?.id === props.rootTypeId)
  return match?.name || ''
})

const displayedRootTypeLabel = computed(() => props.rootTypeLabel || fallbackRootTypeLabel.value)

const formatOptionsText = (field: Record<string, any>) => {
  if (typeof field?.optionsText === 'string') {
    return field.optionsText
  }
  if (Array.isArray(field?.options)) {
    return field.options.join('\n')
  }
  return ''
}

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

const parseOptionsFromText = (text: string) => {
  return text
    .split('\n')
    .map((option) => option.trim())
    .filter((option) => option.length > 0)
}

const applyCustomFieldOptions = (node: Record<string, any> | null | undefined) => {
  if (!node || typeof node !== 'object') {
    return
  }

  if (Array.isArray(node.customFields)) {
    node.customFields = node.customFields.map((field: Record<string, any>) => {
      if (!field || typeof field !== 'object') {
        return field
      }

      const next = { ...field }
      if (next.type === 'select') {
        const baseText = normalizeLineEndings(formatOptionsText(next))
        if (next.optionsText !== baseText) {
          next.optionsText = baseText
        }
        const parsedOptions = parseOptionsFromText(next.optionsText || '')
        if (parsedOptions.length > 0) {
          next.options = parsedOptions
        } else {
          delete next.options
        }
      } else {
        if (next.options !== undefined) {
          delete next.options
        }
        if (next.optionsText !== undefined && next.optionsText !== '') {
          next.optionsText = ''
        }
      }
      return next
    })
  }

  if (Array.isArray(node.subcomponents)) {
    node.subcomponents = node.subcomponents.map((sub: Record<string, any>) => {
      if (!sub || typeof sub !== 'object') {
        return sub
      }
      const copy = { ...sub }
      applyCustomFieldOptions(copy)
      return copy
    })
  }
}

const prepareStructureForEmit = (structure: any) => {
  const clone = cloneStructure(structure)
  applyCustomFieldOptions(clone as Record<string, any>)
  return clone
}

const syncRootType = () => {
  if (!props.lockRootType) {
    previousLockedLabel.value = props.rootTypeLabel || ''
    return
  }

  const newTypeId = props.rootTypeId || ''
  const newLabel = displayedRootTypeLabel.value

  localStructure.typeComposantId = newTypeId
  localStructure.typeComposantLabel = newLabel

  const match = availableComponentTypes.value.find((type) => type?.id === newTypeId)
  if (match?.code) {
    localStructure.familyCode = match.code
  }

  const previousLabel = previousLockedLabel.value
  if (!localStructure.alias || localStructure.alias === previousLabel || localStructure.alias === '') {
    localStructure.alias = newLabel || localStructure.alias
  }

  previousLockedLabel.value = newLabel
}

let lastEmitted = JSON.stringify(prepareStructureForEmit(props.modelValue))

const syncFromProps = (value: any) => {
  const normalizedIncoming = prepareStructureForEmit(value)
  const incomingSerialized = JSON.stringify(normalizedIncoming)
  if (incomingSerialized === lastEmitted) {
    return
  }
  const hydrated = hydrateStructureForEditor(value)
    localStructure.customFields = hydrated.customFields
    localStructure.pieces = hydrated.pieces
    localStructure.products = hydrated.products
    localStructure.subcomponents = hydrated.subcomponents
  localStructure.typeComposantId = hydrated.typeComposantId
  localStructure.typeComposantLabel = hydrated.typeComposantLabel
  localStructure.modelId = hydrated.modelId
  localStructure.familyCode = hydrated.familyCode
  localStructure.alias = hydrated.alias
  lastEmitted = incomingSerialized
  syncRootType()
}

watch(
  () => props.modelValue,
  (value) => {
    syncFromProps(value)
  },
  { deep: true }
)

watch(
  () => [props.rootTypeId, props.rootTypeLabel, props.lockRootType],
  () => {
    syncRootType()
  },
  { immediate: true }
)

watch(
  availableComponentTypes,
  () => {
    syncRootType()
  }
)

watch(
  localStructure,
  (value) => {
    const payload = prepareStructureForEmit(value)
    const serialized = JSON.stringify(payload)
    if (serialized !== lastEmitted) {
      lastEmitted = serialized
      emit('update:modelValue', payload)
    }
  },
  { deep: true }
)

onMounted(async () => {
  const loaders: Promise<unknown>[] = []
  if (!availablePieceTypes.value.length) {
    loaders.push(loadPieceTypes())
  }
  if (!availableComponentTypes.value.length) {
    loaders.push(loadComponentTypes())
  }
  if (!availableProductTypes.value.length) {
    loaders.push(loadProductTypes())
  }
  if (loaders.length) {
    await Promise.allSettled(loaders)
  }
  syncRootType()
})

watch(
  allowSubcomponents,
  (allowed) => {
    if (!allowed && Array.isArray(localStructure.subcomponents) && localStructure.subcomponents.length) {
      localStructure.subcomponents = []
    }
  },
  { immediate: true }
)
</script>
