<template>
    <Dialog :open="visible" @update:open="(open) => (open ? null : closeDialog())">
        <DialogContent class="x-dialog sm:max-w-[50vw] h-[70vh] overflow-hidden">
            <DialogHeader>
                <div class="dialog-title-container">
                    <DialogTitle>{{ t('dialog.group_calendar.header') }}</DialogTitle>
                </div>
                <div class="featured-switch">
                    <span class="featured-switch-text">{{ t('dialog.group_calendar.featured_events') }}</span>
                    <Switch v-model="showFeaturedEvents" @update:modelValue="toggleFeaturedEvents" class="mr-2" />
                    <Button size="sm" variant="outline" @click="toggleViewMode" class="view-toggle-btn">
                        {{
                            viewMode === 'timeline'
                                ? t('dialog.group_calendar.list_view')
                                : t('dialog.group_calendar.calendar_view')
                        }}
                    </Button>
                </div>
            </DialogHeader>
            <div class="top-content">
                <div v-if="viewMode === 'timeline'" key="timeline" class="timeline-view">
                    <div class="timeline-container">
                        <div v-if="groupedTimelineEvents.length" class="timeline-list">
                            <div v-for="(timeGroup, key) of groupedTimelineEvents" :key="key" class="timeline-group">
                                <div class="timeline-timestamp">
                                    {{ dayjs(timeGroup.startsAt).format('MM-DD ddd') }} {{ timeGroup.startTime }}
                                </div>
                                <div class="time-group-container">
                                    <GroupCalendarEventCard
                                        v-for="value in timeGroup.events"
                                        :key="value.id"
                                        :event="value"
                                        mode="timeline"
                                        :is-following="isEventFollowing(value.id)"
                                        :card-class="{ 'grouped-card': timeGroup.events.length > 1 }"
                                        @update-following-calendar-data="updateFollowingCalendarData"
                                        @click-action="showGroupDialog(value.ownerId)" />
                                </div>
                            </div>
                        </div>
                        <div v-else class="timeline-empty">{{ t('dialog.group_calendar.no_events') }}</div>
                    </div>

                    <div class="calendar-container">
                        <GroupCalendarMonth
                            v-model="selectedDay"
                            :is-loading="isLoading"
                            :events-by-date="filteredCalendar"
                            :following-by-date="followingCalendarDate" />
                    </div>
                </div>
                <div v-else key="grid" class="grid-view">
                    <div class="search-container">
                        <InputGroupSearch
                            v-model="searchQuery"
                            size="sm"
                            :placeholder="t('dialog.group_calendar.search_placeholder')"
                            class="search-input" />
                    </div>

                    <div class="groups-grid">
                        <div v-if="filteredGroupEvents.length" class="groups-container">
                            <div v-for="group in filteredGroupEvents" :key="group.groupId" class="group-row">
                                <div class="group-header" @click="toggleGroup(group.groupId)">
                                    <ChevronDown
                                        class="rotation-transition"
                                        :class="{ 'is-rotated': groupCollapsed[group.groupId] }" />
                                    {{ group.groupName }}
                                </div>
                                <div class="events-row" v-show="!groupCollapsed[group.groupId]">
                                    <GroupCalendarEventCard
                                        v-for="event in group.events"
                                        :key="event.id"
                                        :event="event"
                                        mode="grid"
                                        :is-following="isEventFollowing(event.id)"
                                        @update-following-calendar-data="updateFollowingCalendarData"
                                        @click-action="showGroupDialog(event.ownerId)"
                                        card-class="grid-card" />
                                </div>
                            </div>
                        </div>
                        <div v-else class="no-events">
                            {{
                                searchQuery
                                    ? t('dialog.group_calendar.search_no_matching')
                                    : t('dialog.group_calendar.search_no_this_month')
                            }}
                        </div>
                    </div>
                </div>
            </div>
        </DialogContent>
    </Dialog>
</template>

<script setup>
    import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
    import { computed, onMounted, ref, watch } from 'vue';
    import { Button } from '@/components/ui/button';
    import { ChevronDown } from 'lucide-vue-next';
    import { InputGroupSearch } from '@/components/ui/input-group';
    import { useI18n } from 'vue-i18n';

    import dayjs from 'dayjs';

    import { formatDateFilter, getGroupName, replaceBioSymbols } from '../../../shared/utils';
    import { Switch } from '../../../components/ui/switch';
    import { groupRequest } from '../../../api';
    import { processBulk } from '../../../service/request';
    import { useGroupStore } from '../../../stores';

    import GroupCalendarEventCard from '../components/GroupCalendarEventCard.vue';
    import GroupCalendarMonth from '../components/GroupCalendarMonth.vue';
    import configRepository from '../../../service/config';

    const { applyGroupEvent, showGroupDialog } = useGroupStore();

    const { t } = useI18n();

    const props = defineProps({
        visible: {
            type: Boolean,
            required: true
        }
    });

    const emit = defineEmits(['close']);

    const calendar = ref([]);
    const followingCalendar = ref([]);
    const featuredCalendar = ref([]);
    const selectedDay = ref(new Date());
    const isLoading = ref(false);
    const viewMode = ref('timeline');
    const searchQuery = ref('');
    const groupCollapsed = ref({});
    const showFeaturedEvents = ref(false);
    const groupNamesCache = new Map();

    onMounted(async () => {
        showFeaturedEvents.value = await configRepository.getBool('VRCX_groupCalendarShowFeaturedEvents', false);
    });

    function toggleFeaturedEvents() {
        configRepository.setBool('VRCX_groupCalendarShowFeaturedEvents', showFeaturedEvents.value);
        updateCalenderData();
    }

    watch(
        () => props.visible,
        async (newVisible) => {
            if (newVisible) {
                selectedDay.value = new Date();
                updateCalenderData();
            }
        }
    );

    watch(
        () => selectedDay.value,
        async (newDate, oldDate) => {
            if (props.visible && oldDate) {
                const newMonth = dayjs(newDate).format('YYYY-MM');
                const oldMonth = dayjs(oldDate).format('YYYY-MM');

                if (newMonth !== oldMonth) {
                    updateCalenderData();
                }
            }
        }
    );

    async function updateCalenderData() {
        isLoading.value = true;
        let fetchPromises = [getCalendarData(), getFollowingCalendarData()];
        if (showFeaturedEvents.value) {
            fetchPromises.push(getFeaturedCalendarData());
        }
        await Promise.all(fetchPromises)
            .catch((error) => {
                console.error('Error fetching calendar data:', error);
            })
            .finally(() => {
                isLoading.value = false;
            });
    }

    const groupedByGroupEvents = computed(() => {
        const currentMonth = dayjs(selectedDay.value).month();
        const currentYear = dayjs(selectedDay.value).year();

        let currentMonthEvents = calendar.value.filter((event) => {
            const eventDate = dayjs(event.startsAt);
            return eventDate.month() === currentMonth && eventDate.year() === currentYear;
        });
        if (showFeaturedEvents.value) {
            const featuredMonthEvents = featuredCalendar.value.filter((event) => {
                const eventDate = dayjs(event.startsAt);
                return eventDate.month() === currentMonth && eventDate.year() === currentYear;
            });
            currentMonthEvents = currentMonthEvents.concat(featuredMonthEvents);
        }

        const groupMap = new Map();
        currentMonthEvents.forEach((event) => {
            const groupId = event.ownerId;
            if (!groupMap.has(groupId)) {
                groupMap.set(groupId, []);
            }
            groupMap.get(groupId).push(event);
        });

        Array.from(groupMap.values()).forEach((events) => {
            events.sort((a, b) => (dayjs(a.startsAt).isBefore(dayjs(b.startsAt)) ? -1 : 1));
        });

        return Array.from(groupMap.entries()).map(([groupId, events]) => ({
            groupId,
            groupName: groupNamesCache.get(groupId),
            events: events
        }));
    });

    const filteredGroupEvents = computed(() => {
        const hasSearch = searchQuery.value.trim();
        return !hasSearch
            ? groupedByGroupEvents.value
            : groupedByGroupEvents.value.filter((group) => {
                  if (group.groupName.toLowerCase().includes(searchQuery.value.toLowerCase())) return true;

                  return group.events.some(
                      (event) =>
                          event.title?.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
                          event.description?.toLowerCase().includes(searchQuery.value.toLowerCase())
                  );
              });
    });

    watch(
        [filteredGroupEvents, searchQuery],
        ([groups, search]) => {
            const newCollapsed = { ...groupCollapsed.value };
            let hasChanged = false;
            const hasSearch = search.trim();

            groups.forEach((group) => {
                if (!(group.groupId in newCollapsed)) {
                    newCollapsed[group.groupId] = false;
                    hasChanged = true;
                } else if (hasSearch) {
                    newCollapsed[group.groupId] = false;
                    hasChanged = true;
                }
            });

            if (hasChanged) {
                groupCollapsed.value = newCollapsed;
            }
        },
        { immediate: true }
    );

    const filteredCalendar = computed(() => {
        const result = {};
        calendar.value.forEach((item) => {
            const currentDate = formatDateKey(item.startsAt);
            if (!Array.isArray(result[currentDate])) {
                result[currentDate] = [];
            }
            result[currentDate].push(item);
        });
        if (showFeaturedEvents.value) {
            featuredCalendar.value.forEach((item) => {
                const currentDate = formatDateKey(item.startsAt);
                if (!Array.isArray(result[currentDate])) {
                    result[currentDate] = [];
                }
                result[currentDate].push(item);
            });
        }

        Object.values(result).forEach((events) => {
            events.sort((a, b) => dayjs(a.startsAt).diff(dayjs(b.startsAt)));
        });
        return result;
    });

    const followingCalendarDate = computed(() => {
        const result = {};

        const followingIds = new Set(followingCalendar.value.map((item) => item.id));

        calendar.value.forEach((event) => {
            if (followingIds.has(event.id)) {
                const dateKey = formatDateKey(event.startsAt);
                if (!result[dateKey]) {
                    result[dateKey] = [];
                }
                result[dateKey].push(event.id);
            }
        });
        return result;
    });

    const formattedSelectedDay = computed(() => {
        return formatDateKey(selectedDay.value);
    });

    const groupedTimelineEvents = computed(() => {
        const eventsForDay = filteredCalendar.value[formattedSelectedDay.value] || [];
        const timeGroups = {};

        eventsForDay.forEach((event) => {
            const startTimeKey = formatDateFilter(event.startsAt, 'time');
            if (!timeGroups[startTimeKey]) {
                timeGroups[startTimeKey] = [];
            }
            timeGroups[startTimeKey].push(event);
        });

        return Object.entries(timeGroups)
            .map(([startTime, events]) => ({
                startTime,
                events,
                startsAt: events[0].startsAt,
                hasFollowing: events.some((event) => isEventFollowing(event.id))
            }))
            .sort((a, b) => dayjs(a.startsAt).diff(dayjs(b.startsAt)));
    });

    // Use a stable key for calendar maps (independent of locale/appearance date formatting).
    const formatDateKey = (date) => dayjs(date).format('YYYY-MM-DD');

    async function getGroupNameFromCache(groupId) {
        if (!groupNamesCache.has(groupId)) {
            groupNamesCache.set(groupId, await getGroupName(groupId));
        }
    }

    async function getCalendarData() {
        calendar.value = [];
        try {
            await processBulk({
                fn: groupRequest.getGroupCalendars,
                N: -1,
                params: {
                    n: 100,
                    offset: 0,
                    date: dayjs(selectedDay.value).format('YYYY-MM-DDTHH:mm:ss[Z]') // this need to be local time because UTC time may cause month shift
                },
                async handle(args) {
                    for (const event of args.results) {
                        event.title = replaceBioSymbols(event.title);
                        event.description = replaceBioSymbols(event.description);
                        applyGroupEvent(event);
                        await getGroupNameFromCache(event.ownerId);
                    }
                    calendar.value.push(...args.results);
                }
            });
        } catch (error) {
            console.error('Error fetching calendars:', error);
        }
    }

    async function getFollowingCalendarData() {
        followingCalendar.value = [];
        try {
            await processBulk({
                fn: groupRequest.getFollowingGroupCalendars,
                N: -1,
                params: {
                    n: 100,
                    offset: 0,
                    date: dayjs(selectedDay.value).format('YYYY-MM-DDTHH:mm:ss[Z]')
                },
                async handle(args) {
                    for (const event of args.results) {
                        applyGroupEvent(event);
                        await getGroupNameFromCache(event.ownerId);
                    }
                    followingCalendar.value.push(...args.results);
                }
            });
        } catch (error) {
            console.error('Error fetching following calendars:', error);
        }
    }

    async function getFeaturedCalendarData() {
        featuredCalendar.value = [];
        try {
            await processBulk({
                fn: groupRequest.getFeaturedGroupCalendars,
                N: -1,
                params: {
                    n: 100,
                    offset: 0,
                    date: dayjs(selectedDay.value).format('YYYY-MM-DDTHH:mm:ss[Z]')
                },
                async handle(args) {
                    for (const event of args.results) {
                        applyGroupEvent(event);
                        await getGroupNameFromCache(event.ownerId);
                    }
                    featuredCalendar.value.push(...args.results);
                }
            });
        } catch (error) {
            console.error('Error fetching featured calendars:', error);
        }
    }

    function updateFollowingCalendarData(updatedEvent) {
        const index = followingCalendar.value.findIndex((item) => item.id === updatedEvent.id);
        if (index !== -1) {
            followingCalendar.value.splice(index, 1);
        }
        if (updatedEvent.userInterest?.isFollowing) {
            followingCalendar.value.push(updatedEvent);
        }
    }

    function isEventFollowing(eventId) {
        return followingCalendar.value.some((item) => item.id === eventId);
    }

    function toggleViewMode() {
        viewMode.value = viewMode.value === 'timeline' ? 'grid' : 'timeline';
    }

    function toggleGroup(groupId) {
        groupCollapsed.value = {
            ...groupCollapsed.value,
            [groupId]: !groupCollapsed.value[groupId]
        };
    }

    function closeDialog() {
        emit('close');
    }
</script>

<style scoped>
    .x-dialog {
        .top-content {
            height: 640px;
            position: relative;
            overflow: hidden;
            .timeline-view {
                .timeline-container {
                    min-width: 200px;
                    padding-left: 4px;
                    padding-right: 16px;
                    margin-left: 10px;
                    margin-right: 6px;
                    overflow: auto;
                    height: 50vh;

                    .timeline-list {
                        display: flex;
                        flex-direction: column;
                        gap: 14px;
                    }

                    .timeline-group {
                        padding: 0 20px 6px 10px;
                    }

                    .timeline-timestamp {
                        font-size: 13px;
                        font-weight: 600;
                        margin-bottom: 8px;
                    }

                    .timeline-empty {
                        height: 100%;
                        display: flex;
                        align-items: center;
                        justify-content: center;
                    }
                    .time-group-container {
                        display: flex;
                        flex-direction: column;
                        gap: 8px;
                        overflow: visible;
                    }
                }
                .calendar-container {
                    .date {
                        width: 100%;
                        height: 100%;
                        display: flex;
                        align-items: center;
                        justify-content: center;
                        .calendar-date-content {
                            width: 80%;
                            height: 80%;
                            display: flex;
                            align-items: center;
                            justify-content: center;
                            border-radius: 8px;
                            font-size: 18px;
                            position: relative;

                            &.has-events {
                                background-color: var(--group-calendar-event-bg,);
                            }
                            .calendar-event-badge {
                                position: absolute;
                                top: 2px;
                                right: 2px;
                                min-width: 16px;
                                height: 16px;
                                border-radius: 8px;
                                display: flex;
                                align-items: center;
                                justify-content: center;
                                font-size: 10px;
                                font-weight: bold;
                                z-index: 10;
                                padding: 0 4px;
                                line-height: 16px;
                            }
                        }
                    }
                }
            }
        }
    }

    .dialog-title-container {
        display: flex;
        justify-content: space-between;
        align-items: center;
        width: 100%;
        .view-toggle-btn {
            font-size: 12px;
            padding: 8px 12px;
        }
    }

    .featured-switch {
        display: flex;
        justify-content: flex-end;
        align-items: center;
        margin-top: 10px;
        .featured-switch-text {
            font-size: 13px;
            margin-right: 5px;
        }
    }

    .timeline-view {
        display: flex;
        align-items: center;
        .timeline-container {
            flex: 1;
        }
    }

    .grid-view {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        display: flex;
        flex-direction: column;
        .search-container {
            padding: 2px 20px 12px 20px;
            display: flex;
            justify-content: flex-end;
            .search-input {
                width: 300px;
            }
        }

        .groups-grid {
            flex: 1;
            overflow-y: auto;
            padding: 16px 20px;
            .groups-container {
                overflow: visible;
                .group-row {
                    margin-bottom: 18px;
                    overflow: visible;
                    .group-header {
                        font-size: 16px;
                        font-weight: bold;
                        padding: 4px 12px 10px 12px;
                        cursor: pointer;
                        border-radius: 4px;
                        margin: 0 -12px 10px -12px;
                        display: flex;
                        align-items: center;

                        .rotation-transition {
                            font-size: 14px;
                            margin-right: 8px;
                            transition: transform 0.3s;
                        }
                    }
                    .events-row {
                        display: flex;
                        flex-wrap: wrap;
                        gap: 16px;
                        overflow: visible;
                    }
                }
            }
            .no-events {
                display: flex;
                justify-content: center;
                align-items: center;
                height: 200px;
                font-size: 16px;
            }
        }
    }

    .is-rotated {
        transform: rotate(-90deg);
    }

    .rotation-transition {
        transition: transform 0.2s ease-in-out;
    }
</style>
