<template>
    <form>
        <div class="flex flex-row justify-between gap-x-12 font-bold uppercase mb-8">
            <div
                v-for="type in bovineTypes"
                :key="type.id"
            >
                <UiNumberInput
                    :label="type.label"
                    :code="type.code"
                    v-model="localQuantities[String(type.id)]"
                    :disabled="!isAdmin"
                    :placeholder="0"
                    :min="0"
                    :max="10"
                    wrapperClass="w-44 flex-col"
                    inputClass="font-medium"
                />
            </div>
            <UiNumberInput
                label="Autres"
                v-model="localOtherQuantity"
                :disabled="!isAdmin"
                wrapperClass="w-44 flex-col"
                inputClass="font-medium"
            />
        </div>
    </form>
</template>

<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { getBovineTypeList } from '~/services/bovine-type'
import type { BovineTypeData } from '~/services/dto/bovine-type-data'
import type { ReceptionBovineTypeData } from '~/services/dto/reception-bovine-data'

const props = defineProps<{
    modelValue: ReceptionBovineTypeData[]
    otherQuantity: number | null
    isAdmin: boolean
}>()

const emit = defineEmits<{
    (event: 'update:modelValue', value: ReceptionBovineTypeData[]): void
    (event: 'update:otherQuantity', value: number | null): void
}>()

// Types activés par l'admin (display=true), chargés depuis l'API.
const displayedTypes = ref<BovineTypeData[]>([])
// On affiche les types activés ET ceux déjà saisis sur la réception (même masqués),
// pour ne pas faire disparaître/perdre une quantité existante.
const bovineTypes = computed<BovineTypeData[]>(() => {
    const seen = new Set(displayedTypes.value.map((type) => type.id))
    const fromExisting = props.modelValue
        .map((entry) => entry.bovineType)
        .filter((type): type is BovineTypeData => Boolean(type) && !seen.has(type.id))

    return [...displayedTypes.value, ...fromExisting]
})
const localQuantities = reactive<Record<string, number | null>>({})
const localOtherQuantity = ref<number | null>(props.otherQuantity ?? 0)
// Verrou pour éviter les boucles props -> local -> emit -> props.
const isSyncing = ref(false)

function entriesEqualByTypeAndQuantity(
    left: ReceptionBovineTypeData[],
    right: ReceptionBovineTypeData[]
): boolean {
    const toMap = (entries: ReceptionBovineTypeData[]) => {
        const map = new Map<number, number>()
        for (const entry of entries) {
            const typeId = entry.bovineType?.id ?? 0
            map.set(typeId, entry.quantity ?? 0)
        }

        return map
    }

    const a = toMap(left)
    const b = toMap(right)
    if (a.size !== b.size) {
        return false
    }

    for (const [typeId, quantity] of a.entries()) {
        if ((b.get(typeId) ?? 0) !== quantity) {
            return false
        }
    }

    return true
}

function buildEntriesFromLocal(): ReceptionBovineTypeData[] {
    return bovineTypes.value.map((type) => {
        const existing = props.modelValue.find((entry) => entry.bovineType.id === type.id)
        return {
            id: existing?.id ?? 0,
            bovineType: type,
            quantity: localQuantities[String(type.id)] ?? 0
        }
    })
}

function syncLocalFromProps() {
    isSyncing.value = true
    try {
        for (const key of Object.keys(localQuantities)) {
            delete localQuantities[key]
        }

        for (const type of bovineTypes.value) {
            const existing = props.modelValue.find((entry) => entry.bovineType.id === type.id)
            localQuantities[String(type.id)] = existing?.quantity ?? 0
        }
    } finally {
        isSyncing.value = false
    }
}

watch(
    () => props.otherQuantity,
    (value) => {
        if (isSyncing.value) {
            return
        }

        const next = value ?? 0
        isSyncing.value = true
        localOtherQuantity.value = next
        isSyncing.value = false
    }
)

watch(localOtherQuantity, (value) => {
    if (isSyncing.value) {
        return
    }

    const next = value ?? 0
    emit('update:otherQuantity', next)
})

watch(
    () => props.modelValue,
    () => {
        // Hydratation locale uniquement quand le parent change.
        syncLocalFromProps()
    },
    { immediate: true }
)

watch(
    localQuantities,
    () => {
        if (isSyncing.value) {
            return
        }
        // N'émet que si les quantités diffèrent réellement du parent.
        const nextEntries = buildEntriesFromLocal()
        if (!entriesEqualByTypeAndQuantity(nextEntries, props.modelValue)) {
            emit('update:modelValue', nextEntries)
        }
    },
    { deep: true }
)

// Re-synchronise dès que la liste fusionnée change (chargement async des types).
watch(bovineTypes, () => {
    syncLocalFromProps()
})

onMounted(async () => {
    displayedTypes.value = await getBovineTypeList({ display: true })
    syncLocalFromProps()
})
</script>
