<script setup lang="ts">
import type { CalendarOptions, DatesSetArg, EventClickArg, EventContentArg, EventInput } from '@fullcalendar/core';
import idLocale from '@fullcalendar/core/locales/id';
import dayGridPlugin from '@fullcalendar/daygrid';
import interactionPlugin from '@fullcalendar/interaction';
import FullCalendar from '@fullcalendar/vue3';
import { router } from '@inertiajs/vue3';
import { ChevronLeft, ChevronRight } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import AttendanceDetailDialog from '@/components/admin/hr/attendances/AttendanceDetailDialog.vue';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { DatePicker } from '@/components/ui/date-picker';
import { cn } from '@/lib/utils';
import type { AttendanceListItem, CalendarRange } from '@/types/attendance';

const props = defineProps<{
    attendances: AttendanceListItem[];
    calendarRange: CalendarRange;
    canManageAll: boolean;
}>();

const calendarRef = ref<InstanceType<typeof FullCalendar> | null>(null);
const detailOpen = ref(false);
const selectedAttendance = ref<AttendanceListItem | null>(null);
const isNavigating = ref(false);
const calendarTitle = ref('');
const pickerDate = ref(props.calendarRange.start);
const isSyncingPicker = ref(false);

const attendanceById = computed(() => new Map(
    props.attendances.map((attendance) => [attendance.id, attendance]),
));

function getEventClassNames(attendance: AttendanceListItem): string[] {
    return [
        'attendance-event',
        attendance.check_out_at ? 'attendance-event--complete' : 'attendance-event--active',
    ];
}

const events = computed<EventInput[]>(() => props.attendances.map((attendance) => ({
    id: String(attendance.id),
    title: buildEventTitle(attendance),
    start: attendance.attendance_date,
    allDay: true,
    classNames: getEventClassNames(attendance),
    extendedProps: {
        attendanceId: attendance.id,
    },
})));

const calendarOptions = computed<CalendarOptions>(() => ({
    plugins: [dayGridPlugin, interactionPlugin],
    initialView: 'dayGridMonth',
    headerToolbar: false,
    locale: idLocale,
    height: 'auto',
    fixedWeekCount: false,
    dayMaxEvents: 3,
    moreLinkText: (count) => `+${count} lainnya`,
    events: events.value,
    eventClick: handleEventClick,
    eventContent: renderEventContent,
    datesSet: handleDatesSet,
    noEventsContent: 'Tidak ada presensi pada periode ini.',
}));

function buildEventTitle(attendance: AttendanceListItem): string {
    const timeRange = attendance.check_out_at_formatted
        ? `${attendance.check_in_at_formatted} – ${attendance.check_out_at_formatted}`
        : `${attendance.check_in_at_formatted} (belum pulang)`;

    if (props.canManageAll && attendance.employee_name) {
        return `${attendance.employee_name}: ${timeRange}`;
    }

    return timeRange;
}

function renderEventContent(arg: EventContentArg) {
    const attendance = attendanceById.value.get(Number(arg.event.extendedProps.attendanceId));

    if (!attendance) {
        return { html: escapeHtml(arg.event.title) };
    }

    const isComplete = Boolean(attendance.check_out_at);
    const timeLabel = isComplete
        ? `${attendance.check_in_at_formatted} – ${attendance.check_out_at_formatted}`
        : `${attendance.check_in_at_formatted}`;

    const nameMarkup = props.canManageAll && attendance.employee_name
        ? `<span class="attendance-event__name">${escapeHtml(attendance.employee_name)}</span>`
        : '';

    const statusMarkup = isComplete
        ? ''
        : '<span class="attendance-event__status">Belum pulang</span>';

    return {
        html: `
            <div class="attendance-event__inner">
                ${nameMarkup}
                <span class="attendance-event__time">${escapeHtml(timeLabel)}</span>
                ${statusMarkup}
            </div>
        `,
    };
}

function escapeHtml(value: string): string {
    return value
        .replaceAll('&', '&amp;')
        .replaceAll('<', '&lt;')
        .replaceAll('>', '&gt;')
        .replaceAll('"', '&quot;')
        .replaceAll('\'', '&#39;');
}

function toISODate(date: Date): string {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');

    return `${year}-${month}-${day}`;
}

function handleEventClick(arg: EventClickArg) {
    const attendanceId = Number(arg.event.extendedProps.attendanceId);
    const attendance = attendanceById.value.get(attendanceId) ?? null;

    if (!attendance) {
        return;
    }

    selectedAttendance.value = attendance;
    detailOpen.value = true;
}

function handleDatesSet(arg: DatesSetArg) {
    calendarTitle.value = arg.view.title;

    isSyncingPicker.value = true;
    pickerDate.value = toISODate(arg.view.currentStart);
    isSyncingPicker.value = false;

    if (isNavigating.value) {
        return;
    }

    const start = arg.startStr.slice(0, 10);
    const end = arg.endStr.slice(0, 10);

    if (start === props.calendarRange.start && end === props.calendarRange.end) {
        return;
    }

    isNavigating.value = true;

    router.get('/admin/hr/attendances', { start, end }, {
        preserveState: true,
        preserveScroll: true,
        only: ['attendances', 'calendarRange'],
        onFinish: () => {
            isNavigating.value = false;
        },
    });
}

function navigatePrev() {
    calendarRef.value?.getApi().prev();
}

function navigateNext() {
    calendarRef.value?.getApi().next();
}

function navigateToday() {
    calendarRef.value?.getApi().today();
}

watch(pickerDate, (date) => {
    if (!date || isSyncingPicker.value) {
        return;
    }

    calendarRef.value?.getApi().gotoDate(date);
});

watch(
    () => props.attendances,
    () => {
        if (detailOpen.value && selectedAttendance.value) {
            selectedAttendance.value = attendanceById.value.get(selectedAttendance.value.id) ?? null;
        }
    },
);

watch(
    () => props.calendarRange.start,
    (start) => {
        if (!pickerDate.value) {
            pickerDate.value = start;
        }
    },
);
</script>

<template>
    <div class="attendance-calendar space-y-4">
        <div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
            <div class="flex flex-wrap items-center gap-2">
                <div class="bg-muted/50 flex items-center rounded-lg border p-1 shadow-xs">
                    <Button
                        variant="ghost"
                        size="icon-sm"
                        aria-label="Bulan sebelumnya"
                        @click="navigatePrev"
                    >
                        <ChevronLeft class="size-4" />
                    </Button>

                    <Button
                        variant="ghost"
                        size="sm"
                        class="px-3"
                        @click="navigateToday"
                    >
                        Hari ini
                    </Button>

                    <Button
                        variant="ghost"
                        size="icon-sm"
                        aria-label="Bulan berikutnya"
                        @click="navigateNext"
                    >
                        <ChevronRight class="size-4" />
                    </Button>
                </div>

                <DatePicker
                    v-model="pickerDate"
                    class="w-auto min-w-[13rem] sm:min-w-[15rem]"
                    placeholder="Pilih bulan"
                />
            </div>

            <div class="flex flex-wrap items-center gap-2">
                <span class="text-muted-foreground hidden text-sm font-medium sm:inline">
                    {{ calendarTitle }}
                </span>

                <Badge variant="outline" class="border-chart-2/50 bg-chart-2/15 text-chart-2 font-normal dark:text-chart-2">
                    Selesai
                </Badge>
                <Badge variant="outline" class="border-amber-500/50 bg-amber-400 font-normal text-amber-950">
                    Belum pulang
                </Badge>
            </div>
        </div>

        <div
            :class="cn(
                'overflow-hidden rounded-xl border bg-background shadow-xs transition-opacity',
                isNavigating && 'pointer-events-none opacity-60',
            )"
        >
            <FullCalendar ref="calendarRef" :options="calendarOptions" />
        </div>
    </div>

    <AttendanceDetailDialog
        v-model:open="detailOpen"
        :attendance="selectedAttendance"
    />
</template>

<style>
.attendance-calendar .fc {
    --fc-border-color: var(--border);
    --fc-page-bg-color: transparent;
    --fc-neutral-bg-color: var(--muted);
    --fc-neutral-text-color: var(--muted-foreground);
    --fc-today-bg-color: color-mix(in oklch, var(--primary) 6%, transparent);
    --fc-now-indicator-color: var(--primary);
    font-family: inherit;
}

.attendance-calendar .fc .fc-scrollgrid {
    border: 0;
    border-radius: 0;
}

.attendance-calendar .fc .fc-scrollgrid-section > td {
    border: 0;
}

.attendance-calendar .fc .fc-col-header-cell {
    border-color: var(--border);
    background: color-mix(in oklch, var(--muted) 55%, transparent);
    padding: 0.625rem 0;
}

.attendance-calendar .fc .fc-col-header-cell-cushion {
    color: var(--muted-foreground);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-decoration: none;
    text-transform: uppercase;
}

.attendance-calendar .fc .fc-daygrid-day {
    border-color: var(--border);
}

.attendance-calendar .fc .fc-daygrid-day-frame {
    min-height: 6.5rem;
    padding: 0.375rem;
}

.attendance-calendar .fc .fc-daygrid-day-top {
    flex-direction: row;
    justify-content: flex-end;
    margin-bottom: 0.25rem;
}

.attendance-calendar .fc .fc-daygrid-day-number {
    color: var(--foreground);
    font-size: 0.8125rem;
    font-weight: 500;
    line-height: 1;
    padding: 0.25rem 0.45rem;
    text-decoration: none;
}

.attendance-calendar .fc .fc-day-today .fc-daygrid-day-number {
    background: var(--primary);
    border-radius: calc(var(--radius) - 2px);
    color: var(--primary-foreground);
}

.attendance-calendar .fc .fc-day-other .fc-daygrid-day-number {
    color: var(--muted-foreground);
    opacity: 0.65;
}

.attendance-calendar .fc .fc-daygrid-day-events {
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
    margin-top: 0.125rem;
}

.attendance-calendar .fc .fc-daygrid-event-harness {
    margin: 0 !important;
}

.attendance-calendar .fc .fc-daygrid-event {
    margin: 0 !important;
}

.attendance-calendar .fc .fc-daygrid-more-link {
    color: var(--muted-foreground);
    font-size: 0.6875rem;
    font-weight: 500;
}

.attendance-calendar .fc .fc-daygrid-more-link:hover {
    background: var(--accent);
    color: var(--accent-foreground);
}

.attendance-calendar .fc .fc-popover {
    background: var(--popover);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: 0 10px 30px color-mix(in oklch, var(--foreground) 8%, transparent);
}

.attendance-calendar .fc .fc-popover-header {
    background: var(--muted);
    color: var(--foreground);
    font-size: 0.8125rem;
    font-weight: 600;
    padding: 0.5rem 0.75rem;
}

.attendance-calendar .fc .attendance-event {
    border: 0;
    border-radius: calc(var(--radius) - 4px);
    box-shadow: none;
    cursor: pointer;
    margin: 0;
    overflow: hidden;
    padding: 0.25rem 0.375rem;
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.attendance-calendar .fc .attendance-event:hover {
    box-shadow: 0 1px 4px color-mix(in oklch, var(--foreground) 10%, transparent);
    transform: translateY(-1px);
}

.attendance-calendar .fc .attendance-event--complete,
.attendance-calendar .fc .attendance-event--complete .fc-event-main {
    background-color: var(--chart-2) !important;
    border-color: color-mix(in oklch, var(--chart-2) 70%, var(--foreground)) !important;
    color: oklch(0.99 0 0) !important;
}

.attendance-calendar .fc .attendance-event--active,
.attendance-calendar .fc .attendance-event--active .fc-event-main {
    background-color: oklch(0.79 0.16 75) !important;
    border-color: oklch(0.7 0.14 70) !important;
    color: oklch(0.32 0.07 55) !important;
}

.attendance-calendar .fc .attendance-event__inner {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    line-height: 1.25;
    min-width: 0;
}

.attendance-calendar .fc .attendance-event__name {
    font-size: 0.6875rem;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.attendance-calendar .fc .attendance-event__time {
    font-size: 0.6875rem;
    font-weight: 500;
    opacity: 0.95;
}

.attendance-calendar .fc .attendance-event__status {
    align-self: flex-start;
    background: color-mix(in oklch, currentColor 12%, transparent);
    border: 1px solid color-mix(in oklch, currentColor 18%, transparent);
    border-radius: calc(var(--radius) - 6px);
    font-size: 0.625rem;
    font-weight: 600;
    line-height: 1;
    padding: 0.125rem 0.375rem;
}

.attendance-calendar .fc .attendance-event--complete .attendance-event__time,
.attendance-calendar .fc .attendance-event--complete .attendance-event__name {
    color: oklch(0.99 0 0);
    opacity: 0.95;
}

.attendance-calendar .fc .attendance-event--active .attendance-event__time,
.attendance-calendar .fc .attendance-event--active .attendance-event__name,
.attendance-calendar .fc .attendance-event--active .attendance-event__status {
    color: oklch(0.32 0.07 55);
    opacity: 0.95;
}

.dark .attendance-calendar .fc .attendance-event--active,
.dark .attendance-calendar .fc .attendance-event--active .fc-event-main {
    background-color: oklch(0.72 0.14 75) !important;
    border-color: oklch(0.64 0.12 70) !important;
    color: oklch(0.28 0.06 55) !important;
}

.dark .attendance-calendar .fc .attendance-event--active .attendance-event__time,
.dark .attendance-calendar .fc .attendance-event--active .attendance-event__name,
.dark .attendance-calendar .fc .attendance-event--active .attendance-event__status {
    color: oklch(0.28 0.06 55);
}

@media (max-width: 640px) {
    .attendance-calendar .fc .fc-daygrid-day-frame {
        min-height: 5rem;
    }

    .attendance-calendar .fc .attendance-event__name,
    .attendance-calendar .fc .attendance-event__time {
        font-size: 0.625rem;
    }
}
</style>
