<template>
    <div>
        <div class="flex flex-wrap items-center justify-between gap-4">
            <h1 class="text-4xl font-bold text-primary-500">Calendrier des absences</h1>
        </div>
        <div class="flex items-center justify-between py-6">
            <div class="flex items-center gap-4">
                <div class="flex flex-wrap items-center gap-4 rounded-md border border-neutral-300 px-3 py-2">
                    <div v-for="site in sites" :key="site.id" class="flex items-center gap-2">
                        <div :style="{ backgroundColor: site.color }" class="h-4 w-4 rounded"></div>
                        <label class="text-md" :for="`site-${site.id}`">{{ site.name }}</label>
                        <input
                            :id="`site-${site.id}`"
                            v-model="selectedSiteIds"
                            :value="site.id"
                            type="checkbox"
                            class="h-4 w-4"
                        />
                    </div>
                </div>
                <select
                    v-model="selectedMonth"
                    class="h-10 rounded-md border border-neutral-300 bg-white px-3 text-md text-neutral-900"
                >
                    <option v-for="month in months" :key="month.value" :value="month.value">
                        {{ month.label }}
                    </option>
                </select>
                <select
                    v-model="selectedYear"
                    class="h-10 rounded-md border border-neutral-300 bg-white px-3 text-md text-neutral-900"
                >
                    <option v-for="year in years" :key="year" :value="year">
                        {{ year }}
                    </option>
                </select>
            </div>
            <div class="flex gap-4">
                <button
                    type="button"
                    class="h-10 rounded-lg bg-primary-500 px-4 text-md font-semibold text-white hover:bg-secondary-500"
                    @click="openCreateFromToday"
                >
                    Ajouter une absence
                </button>
                <button
                    type="button"
                    class="h-10 rounded-lg bg-primary-500 px-4 text-md font-semibold text-white hover:bg-secondary-500"
                    @click="openPrint"
                >
                    Imprimer
                </button>
            </div>
        </div>

        <CalendarGrid
            :days-in-month="daysInMonth"
            :visible-employees="visibleEmployees"
            :grid-style="gridStyle"
            :get-cell-style="getCellStyle"
            :get-cell-info="getCellInfo"
            :format-employee-name="formatEmployeeName"
            :is-holiday-date="isHolidayDate"
            @cell-click="openCreate"
        />

        <AbsenceFormDrawer
            v-model="isDrawerOpen"
            :employees="employees"
            :absence-types="absenceTypes"
            :form="form"
            :editing-absence="editingAbsence"
            :is-submitting="isSubmitting"
            @submit="handleSubmit"
            @delete="handleDelete"
            @cancel="closeDrawer"
        />

        <AbsencePrintDrawer
            v-model="isPrintOpen"
            :sites="sites"
            :print-form="printForm"
            @submit="handlePrint"
            @cancel="closePrint"
        />
    </div>
</template>

<script setup lang="ts">
import type {Employee} from '~/services/dto/employee'
import type {AbsenceType} from '~/services/dto/absence-type'
import type {Absence} from '~/services/dto/absence'
import type {HalfDay} from '~/services/dto/half-day'
import {HALF_DAYS} from '~/services/dto/half-day'
import {listEmployees} from '~/services/employees'
import {listAbsenceTypes} from '~/services/absence-types'
import {createAbsence, deleteAbsence, listAbsences, updateAbsence} from '~/services/absences'
import {listPublicHolidays} from '~/services/public-holidays'
import {getDaysInMonth, normalizeDate, toYmd} from '~/utils/date'
import CalendarGrid from '~/components/CalendarGrid.vue'
import AbsenceFormDrawer from '~/components/AbsenceFormDrawer.vue'
import AbsencePrintDrawer from '~/components/AbsencePrintDrawer.vue'

// Données principales affichées dans la grille.
const employees = ref<Employee[]>([])
const sites = computed(() => {
    const siteMap = new Map<number, { id: number; name: string; color: string }>()
    for (const employee of employees.value) {
        if (employee.site) {
            siteMap.set(employee.site.id, employee.site)
        }
    }
    return Array.from(siteMap.values()).sort((siteA, siteB) => siteA.name.localeCompare(siteB.name, 'fr'))
})

// Filtres de sites (par défaut: tous sélectionnés à l'init).
const selectedSiteIds = ref<number[]>([])
const sitesInitialized = ref(false)

watch(sites, (next) => {
    if (sitesInitialized.value || next.length === 0) return
    selectedSiteIds.value = next.map((site) => site.id)
    sitesInitialized.value = true
}, { immediate: true })

// Tri stable: site -> nom -> prénom.
const sortedEmployees = computed(() => {
  return [...employees.value].sort((employeeA, employeeB) => {
    const siteNameA = employeeA.site?.name ?? ''
    const siteNameB = employeeB.site?.name ?? ''
    if (siteNameA !== siteNameB) return siteNameA.localeCompare(siteNameB, 'fr')
        const lastNameA = employeeA.lastName ?? ''
        const lastNameB = employeeB.lastName ?? ''
        if (lastNameA !== lastNameB) return lastNameA.localeCompare(lastNameB, 'fr')
        const firstNameA = employeeA.firstName ?? ''
        const firstNameB = employeeB.firstName ?? ''
    return firstNameA.localeCompare(firstNameB, 'fr')
  })
})

// Employés visibles selon le filtre de sites.
const visibleEmployees = computed(() => {
    if (selectedSiteIds.value.length === 0) return []
    return sortedEmployees.value.filter((employee) => {
        return employee.site?.id && selectedSiteIds.value.includes(employee.site.id)
    })
})
// Données de référence et absences du mois affiché.
const absenceTypes = ref<AbsenceType[]>([])
const absences = ref<Absence[]>([])
const publicHolidays = ref<Record<string, string>>({})

// États UI.
const isDrawerOpen = ref(false)
const isSubmitting = ref(false)
const editingAbsence = ref<Absence | null>(null)
const isPrintOpen = ref(false)

// Sélecteurs de période.
const now = new Date()
const selectedMonth = ref(now.getMonth())
const selectedYear = ref(now.getFullYear())

const months = [
    {value: 0, label: 'Janvier'},
    {value: 1, label: 'Février'},
    {value: 2, label: 'Mars'},
    {value: 3, label: 'Avril'},
    {value: 4, label: 'Mai'},
    {value: 5, label: 'Juin'},
    {value: 6, label: 'Juillet'},
    {value: 7, label: 'Août'},
    {value: 8, label: 'Septembre'},
    {value: 9, label: 'Octobre'},
    {value: 10, label: 'Novembre'},
    {value: 11, label: 'Décembre'}
]

const years = Array.from({length: 5}, (unusedValue, index) => now.getFullYear() - 2 + index)


// Infos de calendrier calculées.
const daysInMonth = computed(() => getDaysInMonth(selectedYear.value, selectedMonth.value))
const monthStartDate = computed(() => new Date(selectedYear.value, selectedMonth.value, 1))
const monthEndDate = computed(() => new Date(selectedYear.value, selectedMonth.value + 1, 0))

// Largeur fixe de la colonne employés + une colonne par jour.
const gridStyle = computed(() => ({
    gridTemplateColumns: `160px repeat(${daysInMonth.value.length}, minmax(44px, 1fr))`
}))

// Formulaire d'absence (AM/PM par défaut = journée complète).
const form = reactive({
    employeeId: '' as number | '',
    typeId: '' as number | '',
    startDate: '',
    startHalf: 'AM' as HalfDay,
    endDate: '',
    endHalf: 'PM' as HalfDay,
    comment: ''
})

// Formulaire d'impression (intervalle + sites).
const printForm = reactive({
    from: '',
    to: '',
    siteIds: [] as number[]
})

// Remet le formulaire à zéro.
const resetForm = () => {
    form.employeeId = ''
    form.typeId = ''
    form.startDate = ''
    form.startHalf = 'AM'
    form.endDate = ''
    form.endHalf = 'PM'
    form.comment = ''
}

// Ferme le drawer et nettoie l'état.
const closeDrawer = () => {
    isDrawerOpen.value = false
    editingAbsence.value = null
    resetForm()
}

// Ouvre l'impression avec la période du mois courant.
const openPrint = () => {
    const monthStart = toYmd(selectedYear.value, selectedMonth.value, 1)
    const monthEnd = toYmd(selectedYear.value, selectedMonth.value + 1, 0)
    printForm.from = monthStart
    printForm.to = monthEnd
    printForm.siteIds = [...selectedSiteIds.value]
    isPrintOpen.value = true
}

const closePrint = () => {
    isPrintOpen.value = false
}

// Parse "YYYY-MM-DD" en Date (ou null).
const parseYmd = (value: string) => {
    const [year, month, day] = value.split('-').map(Number)
    if (!year || !month || !day) return null
    return new Date(year, month - 1, day)
}

// Détermine si la journée est une demi-journée (AM/PM) ou complète.
const getHalfForDate = (
    startDate: string,
    endDate: string,
    startHalf: HalfDay,
    endHalf: HalfDay,
    date: string
) => {
    if (startDate === endDate) {
        return startHalf === endHalf ? startHalf : null
    }
    if (date === startDate && startHalf === 'PM') return 'PM'
    if (date === endDate && endHalf === 'AM') return 'AM'
    return null
}

// Renvoie les segments occupés pour une date donnée (AM/PM).
const getSegmentsForDate = (
    startDate: string,
    endDate: string,
    startHalf: HalfDay,
    endHalf: HalfDay,
    date: string
) => {
    const half = getHalfForDate(startDate, endDate, startHalf, endHalf, date)
    if (!half) return HALF_DAYS.map((item) => item.value) as HalfDay[]
    return [half] as HalfDay[]
}

// Ajoute des mois tout en gardant un jour valide.
const addMonths = (date: Date, months: number) => {
    const next = new Date(date.getFullYear(), date.getMonth() + months, date.getDate())
    if (next.getMonth() !== (date.getMonth() + months) % 12) {
        next.setDate(0)
    }
    return next
}

// Limite l'intervalle d'impression à 2 mois max.
const enforcePrintRange = () => {
    if (!printForm.from) return
    const start = parseYmd(printForm.from)
    if (!start) return
    const maxEnd = addMonths(start, 2)
    maxEnd.setDate(maxEnd.getDate() - 1)
    const maxEndYmd = toYmd(maxEnd.getFullYear(), maxEnd.getMonth(), maxEnd.getDate())

    if (!printForm.to) {
        printForm.to = maxEndYmd
        return
    }

    const end = parseYmd(printForm.to)
    if (!end) {
        printForm.to = maxEndYmd
        return
    }

    if (end < start) {
        printForm.to = printForm.from
        return
    }

    if (end > maxEnd) {
        printForm.to = maxEndYmd
    }
}

watch(() => printForm.from, enforcePrintRange)
watch(() => printForm.to, enforcePrintRange)

// Chargements API.
const loadEmployees = async () => {
    employees.value = await listEmployees()
}

const loadAbsenceTypes = async () => {
    absenceTypes.value = await listAbsenceTypes()
}

const loadPublicHolidays = async () => {
    publicHolidays.value = await listPublicHolidays('metropole', selectedYear.value)
}

const loadAbsences = async () => {
    const monthStart = toYmd(selectedYear.value, selectedMonth.value, 1)
    const monthEnd = toYmd(selectedYear.value, selectedMonth.value + 1, 0)
    absences.value = await listAbsences({
        from: monthStart,
        to: monthEnd,
        siteIds: selectedSiteIds.value
    })
}

onMounted(async () => {
    await Promise.all([loadEmployees(), loadAbsenceTypes(), loadPublicHolidays(), loadAbsences()])
})

watch([selectedMonth, selectedYear, selectedSiteIds], async () => {
    await loadAbsences()
})

watch(selectedYear, async () => {
    await loadPublicHolidays()
})

// Indexation des absences par cellule pour eviter un find() a chaque case.
const cellAbsenceMap = computed(() => {
    const map = new Map<string, { id: number; code: string; color: string; halfLabel?: HalfDay; textColor?: string }>()
    const monthStart = monthStartDate.value
    const monthEnd = monthEndDate.value

    for (const absence of absences.value) {
        const employeeId = absence.employee?.id
        if (!employeeId) continue
        const startDate = normalizeDate(absence.startDate)
        const endDate = normalizeDate(absence.endDate)
        const start = parseYmd(startDate)
        const end = parseYmd(endDate)
        if (!start || !end) continue

        const rangeStart = start < monthStart ? monthStart : start
        const rangeEnd = end > monthEnd ? monthEnd : end
        if (rangeEnd < rangeStart) continue

        for (
            let currentDate = new Date(rangeStart.getTime());
            currentDate <= rangeEnd;
            currentDate.setDate(currentDate.getDate() + 1)
        ) {
            const dateKey = toYmd(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate())
            const key = `${employeeId}-${dateKey}`
            const halfLabel = getHalfForDate(
                startDate,
                endDate,
                absence.startHalf ?? 'AM',
                absence.endHalf ?? 'PM',
                dateKey
            ) ?? undefined
            map.set(key, {
                id: absence.id,
                code: absence.type?.code ?? '',
                color: absence.type?.color ?? '#222783',
                halfLabel
            })
        }
    }

    return map
})

// Jours fériés (interdit pour la création).
const isHolidayDate = (date: string) => {
    return Boolean(publicHolidays.value[date])
}

// Renvoie l'absence effective pour une cellule (ou un "Férié").
const getCellAbsence = (employeeId: number, date: string) => {
    if (isHolidayDate(date)) {
        return {
            id: 0,
            code: 'Férié',
            color: '#b3e5fc',
            textColor: '#0f172a'
        }
    }
    const absence = cellAbsenceMap.value.get(`${employeeId}-${date}`)
    if (absence) return absence
    return null
}

// Style de cellule (plein ou demi-journée).
const getCellStyle = (employeeId: number, date: string) => {
    const absence = getCellAbsence(employeeId, date)
    if (!absence) return undefined

    if (absence.halfLabel) {
        const color = absence.color
        const textColor = absence.textColor ?? '#FFF'
        const backgroundImage = absence.halfLabel === 'AM'
            ? `linear-gradient(180deg, ${color} 0 50%, transparent 50% 100%)`
            : `linear-gradient(180deg, transparent 0 50%, ${color} 50% 100%)`
        return {
            backgroundImage,
            backgroundColor: 'transparent',
            color: textColor
        }
    }

    return {
        backgroundColor: absence.color,
        color: absence.textColor ?? '#fff'
    }
}

const getCellInfo = (employeeId: number, date: string) => {
    return getCellAbsence(employeeId, date)
}

// Ouverture du drawer depuis une cellule.
const openCreate = (employee: Employee, date: string) => {
    if (isHolidayDate(date)) {
        window.alert("Impossible de creer une absence un jour ferie.")
        return
    }

    const existing = absences.value.find((absence) => {
        const start = normalizeDate(absence.startDate)
        const end = normalizeDate(absence.endDate)
        return absence.employee?.id === employee.id && date >= start && date <= end
    })

    if (existing) {
        editingAbsence.value = existing
        form.employeeId = existing.employee.id
        form.typeId = existing.type.id
        form.startDate = normalizeDate(existing.startDate)
        form.endDate = normalizeDate(existing.endDate)
        form.startHalf = existing.startHalf ?? 'AM'
        form.endHalf = existing.endHalf ?? 'PM'
        form.comment = existing.comment ?? ''
    } else {
        editingAbsence.value = null
        form.employeeId = employee.id
        form.startDate = date
        form.endDate = date
        form.startHalf = 'AM'
        form.endHalf = 'PM'
        form.typeId = ''
        form.comment = ''
    }

    isDrawerOpen.value = true
}

// Ouverture du drawer depuis le bouton "Ajouter".
const openCreateFromToday = () => {
    editingAbsence.value = null
    form.employeeId = ''
    form.typeId = ''
    const now = new Date()
    const today = toYmd(now.getFullYear(), now.getMonth(), now.getDate())
    if (isHolidayDate(today)) {
        window.alert("Impossible de creer une absence un jour ferie.")
        return
    }
    form.startDate = today
    form.endDate = today
    form.startHalf = 'AM'
    form.endHalf = 'PM'
    form.comment = ''
    isDrawerOpen.value = true
}

// Vérifie la présence d'un férié dans l'intervalle.
const hasHolidayInRange = (startDate: string, endDate: string) => {
    const start = parseYmd(startDate)
    const end = parseYmd(endDate)
    if (!start || !end) return false
    for (
        let currentDate = new Date(start.getTime());
        currentDate <= end;
        currentDate.setDate(currentDate.getDate() + 1)
    ) {
        const key = toYmd(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate())
        if (isHolidayDate(key)) {
            return true
        }
    }
    return false
}

// Soumission du formulaire: validations + chevauchement + save.
const handleSubmit = async () => {
    if (isSubmitting.value) return

    isSubmitting.value = true
    try {
        const start = normalizeDate(form.startDate)
        const end = normalizeDate(form.endDate)
        if (start > end) {
            window.alert("La date de fin ne peut pas etre avant la date de debut.")
            return
        }
        if (start === end && form.startHalf === 'PM' && form.endHalf === 'AM') {
            window.alert("La demi-journee de fin ne peut pas etre avant la demi-journee de debut.")
            return
        }
        if (hasHolidayInRange(start, end)) {
            window.alert("Impossible de creer une absence sur un jour ferie.")
            return
        }
        const overlaps = absences.value.filter((absence) => {
            if (absence.employee?.id !== Number(form.employeeId)) return false
            if (editingAbsence.value && absence.id === editingAbsence.value.id) return false
            const aStart = normalizeDate(absence.startDate)
            const aEnd = normalizeDate(absence.endDate)
            if (start > aEnd || end < aStart) return false

            const overlapStart = start > aStart ? start : aStart
            const overlapEnd = end < aEnd ? end : aEnd
            const overlapStartDate = parseYmd(overlapStart)
            const overlapEndDate = parseYmd(overlapEnd)
            if (!overlapStartDate || !overlapEndDate) return false

            for (
                let currentDate = new Date(overlapStartDate.getTime());
                currentDate <= overlapEndDate;
                currentDate.setDate(currentDate.getDate() + 1)
            ) {
                const dateKey = toYmd(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate())
                const existingSegments = getSegmentsForDate(
                    aStart,
                    aEnd,
                    absence.startHalf ?? 'AM',
                    absence.endHalf ?? 'PM',
                    dateKey
                )
                const newSegments = getSegmentsForDate(
                    start,
                    end,
                    form.startHalf,
                    form.endHalf,
                    dateKey
                )
                if (existingSegments.some((segment) => newSegments.includes(segment))) {
                    return true
                }
            }

            return false
        })

        if (overlaps.length > 0) {
            // Securise le chevauchement: on demande confirmation avant suppression.
            const confirmReplace = window.confirm(
                "Cette absence chevauche une autre. Voulez-vous la remplacer ?"
            )
            if (!confirmReplace) return
            for (const overlap of overlaps) {
                await deleteAbsence(overlap.id)
            }
        }

        if (editingAbsence.value) {
            await updateAbsence({
                id: editingAbsence.value.id,
                employeeId: Number(form.employeeId),
                typeId: Number(form.typeId),
                startDate: form.startDate,
                startHalf: form.startHalf,
                endDate: form.endDate,
                endHalf: form.endHalf,
                comment: form.comment
            })
        } else {
            await createAbsence({
                employeeId: Number(form.employeeId),
                typeId: Number(form.typeId),
                startDate: form.startDate,
                startHalf: form.startHalf,
                endDate: form.endDate,
                endHalf: form.endHalf,
                comment: form.comment
            })
        }

        closeDrawer()
        await loadAbsences()
    } finally {
        isSubmitting.value = false
    }
}

// Suppression de l'absence en cours d'édition.
const handleDelete = async () => {
    if (!editingAbsence.value) return

    const confirmDelete = window.confirm('Supprimer cette absence ?')
    if (!confirmDelete) return

    await deleteAbsence(editingAbsence.value.id)
    closeDrawer()
    await loadAbsences()
}

// Affiche "Prénom N.".
const formatEmployeeName = (employee: Employee) => {
    const initial = employee.lastName ? `${employee.lastName[0].toUpperCase()}.` : ''
    return `${employee.firstName} ${initial}`.trim()
}

// Impression PDF de l'intervalle sélectionné.
const { printPdf } = usePdfPrinter()
const handlePrint = async () => {
    const params = new URLSearchParams()
    params.set('from', printForm.from)
    params.set('to', printForm.to)
    if (printForm.siteIds.length > 0) {
        params.set('sites', printForm.siteIds.join(','))
    }
    await printPdf(`/absences/print?${params.toString()}`)
    isPrintOpen.value = false
}
</script>
