<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 './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';
import { index } from '@/routes/admin/hr/attendances';

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)`;

    const duration = attendance.work_duration_formatted
        ? ` (${attendance.work_duration_formatted})`
        : '';

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

    return `${timeRange}${duration}`;
}

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 durationMarkup = attendance.work_duration_formatted
        ? `<span class="attendance-event__duration">${escapeHtml(attendance.work_duration_formatted)}</span>`
        : '';

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

    return {
        html: `
            <div class="attendance-event__inner">
                ${nameMarkup}
                <span class="attendance-event__time">${escapeHtml(timeLabel)}</span>
                ${durationMarkup}
            </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 scrollToToday() {
    requestAnimationFrame(() => {
        const container = calendarRef.value?.$el?.closest('.overflow-x-auto');
        const todayEl = container?.querySelector('.fc-day-today');
        if (!container || !todayEl) {
            return;
        }

        const containerRect = container.getBoundingClientRect();
        const todayRect = todayEl.getBoundingClientRect();
        const offset = todayRect.left - containerRect.left - (containerRect.width / 2) + (todayRect.width / 2);

        container.scrollBy({ left: offset, behavior: 'smooth' });
    });
}

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

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

    scrollToToday();

    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(index.url(), { 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();
    scrollToToday();
}

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-full sm:w-auto 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-emerald-600/50 bg-emerald-500/15 font-normal text-emerald-700 dark:text-emerald-400">
                    Selesai
                </Badge>
                <Badge variant="outline" class="border-primary/50 bg-primary/20 font-normal text-primary-foreground dark:text-white">
                    Belum pulang
                </Badge>
            </div>
        </div>

        <div :class="cn(
            'overflow-x-auto rounded-xl border bg-background shadow-xs transition-opacity',
            isNavigating && 'pointer-events-none opacity-60',
        )">
            <div class="min-w-[640px]">
                <FullCalendar ref="calendarRef" :options="calendarOptions" />
            </div>
        </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 .fc-popover-body {
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
    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: oklch(0.59 0.14 152) !important;
    border-color: oklch(0.52 0.12 152) !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: var(--primary) !important;
    border-color: color-mix(in oklch, var(--primary) 80%, var(--foreground)) !important;
    color: var(--primary-foreground) !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__duration {
    font-size: 0.6875rem;
    font-weight: 500;
    opacity: 0.85;
}

.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,
.attendance-calendar .fc .attendance-event--complete .attendance-event__duration {
    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,
.attendance-calendar .fc .attendance-event--active .attendance-event__duration {
    color: var(--primary-foreground);
    opacity: 0.95;
}

.dark .attendance-calendar .fc .attendance-event--complete,
.dark .attendance-calendar .fc .attendance-event--complete .fc-event-main {
    background-color: oklch(0.55 0.13 152) !important;
    border-color: oklch(0.48 0.11 152) !important;
}

.dark .attendance-calendar .fc .attendance-event--active,
.dark .attendance-calendar .fc .attendance-event--active .fc-event-main {
    background-color: var(--primary) !important;
    border-color: color-mix(in oklch, var(--primary) 75%, var(--foreground)) !important;
    color: var(--primary-foreground) !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,
.dark .attendance-calendar .fc .attendance-event--active .attendance-event__duration {
    color: var(--primary-foreground);
}

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

    .attendance-calendar .fc .fc-col-header-cell {
        padding: 0.375rem 0;
    }

    .attendance-calendar .fc .fc-col-header-cell-cushion {
        font-size: 0.625rem;
    }

    .attendance-calendar .fc .fc-daygrid-day-number {
        font-size: 0.6875rem;
        padding: 0.125rem 0.25rem;
    }

    .attendance-calendar .fc .attendance-event {
        padding: 0.125rem 0.25rem;
    }

    .attendance-calendar .fc .attendance-event__name,
    .attendance-calendar .fc .attendance-event__time {
        font-size: 0.5625rem;
    }

    .attendance-calendar .fc .attendance-event__duration {
        display: none;
    }

    .attendance-calendar .fc .fc-daygrid-more-link {
        font-size: 0.5625rem;
    }
}
</style>
