<script setup lang="ts">
import { addWeeks, getISODay, getISOWeek, isBefore, isSameDay, isSameWeek, startOfISOWeek, subWeeks } from 'date-fns'
import { computed, ref, watch } from 'vue'
import { useFdsI18n } from '../../plugin/useFdsI18n'

import FdsButtonIcon from '../Buttons/FdsButtonIcon/FdsButtonIcon.vue'
import FdsSpinner from '../FdsSpinner/FdsSpinner.vue'
import type { FdsWeekCalendarProps, WeekDateRange } from './types'
import {
  checkIfDateIsBeforeToday,
  checkIfDateIsEnabled,
  getDaysInWeek,
  getDaysOfLastWeek,
  getOverlappingMonths,
  getStartAndEndRangeFromWeek,
} from './weekCalendar.utils'
import WeekDay from './WeekDay.vue'

const props = withDefaults(defineProps<FdsWeekCalendarProps>(), {
  loading: undefined,
  enabledDates: () => [],
  disablePreviousWeek: false,
  disableNextWeek: false,
  enableHorizontalAnimation: true,
  minWeek: undefined,
  maxDate: undefined,
  previewOtherDays: false,
  selected: null,
  weekLabel: 'vecka',
})

const emit = defineEmits<{
  (e: 'change-date-range', event: WeekDateRange, week?: Date): void
  (e: 'select-date', event: Date | null): void
  (e: 'decrement-week'): void
  (e: 'increment-week'): void
}>()

const { locale, t } = useFdsI18n()

const currentWeek = ref(props.minWeek ?? startOfISOWeek(new Date()))
const selectedDay = ref<Date | null>(props.selected ?? null)
const slideDirection = ref<'left' | 'right'>('left')

const weekToRender = computed(() => getDaysInWeek(currentWeek.value))
const visibleWeekDays = computed(() => weekToRender.value.filter((day) => getISODay(day) <= 5))
const lastWeek = computed(() => getDaysOfLastWeek(subWeeks(currentWeek.value, 1)))

const onHandleSelectedDay = (weekDay: Date | null) => {
  selectedDay.value = weekDay
  emit('select-date', weekDay)
}

const onHandleSetCurrentWeek = (week: Date) => {
  if (selectedDay.value && !isSameWeek(selectedDay.value, week, { weekStartsOn: 1 })) {
    onHandleSelectedDay(null)
  }
  currentWeek.value = week
}

const weekText = computed(
  () =>
    `${getOverlappingMonths(currentWeek.value, locale.value)} (${t('FdsWeekCalendar.week')} ${getISOWeek(currentWeek.value)})`,
)

const lastDayOfPreviousWeek = computed(() => lastWeek.value[lastWeek.value.length - 1])
const disableLastWeekInteraction = computed(
  () =>
    !!props.disablePreviousWeek ||
    (!!lastDayOfPreviousWeek.value && checkIfDateIsBeforeToday(lastDayOfPreviousWeek.value)) ||
    (!!props.minWeek && isBefore(subWeeks(currentWeek.value, 1), startOfISOWeek(props.minWeek))),
)
const disableNextWeekInteraction = computed(
  () =>
    !!props.disableNextWeek || (!!props.maxDate && isSameWeek(currentWeek.value, props.maxDate, { weekStartsOn: 1 })),
)
const transitionClasses = computed(() => {
  if (!props.enableHorizontalAnimation) {
    return {
      enterActiveClass: '',
      leaveActiveClass: '',
      enterFromClass: '',
      leaveToClass: '',
    }
  }

  const activeClass = 'absolute inset-0 transition-all duration-300 ease-[cubic-bezier(0.22,0.61,0.36,1)]'
  if (slideDirection.value === 'left') {
    return {
      enterActiveClass: activeClass,
      leaveActiveClass: activeClass,
      enterFromClass: 'translate-x-full opacity-100',
      leaveToClass: '-translate-x-full opacity-100',
    }
  }

  return {
    enterActiveClass: activeClass,
    leaveActiveClass: activeClass,
    enterFromClass: '-translate-x-full opacity-100',
    leaveToClass: 'translate-x-full opacity-100',
  }
})

watch(
  visibleWeekDays,
  (week) => {
    const { endDate, startDate } = getStartAndEndRangeFromWeek(week)
    if (!startDate || !endDate) return
    emit('change-date-range', { endDate, startDate }, currentWeek.value)
  },
  { immediate: true },
)

watch(
  () => props.minWeek,
  (newWeek) => {
    if (!newWeek) {
      currentWeek.value = startOfISOWeek(new Date())
      return
    }
    if (newWeek.toISOString() === currentWeek.value.toISOString()) return
    currentWeek.value = newWeek
  },
)

watch(
  () => props.selected,
  (newSelected) => {
    selectedDay.value = newSelected ?? null
    emit('select-date', newSelected ?? null)
  },
)

watch(
  () => props.goToWeek?.getTime(),
  (newTimestamp) => {
    if (!newTimestamp) return
    const normalizedWeek = startOfISOWeek(new Date(newTimestamp))
    if (isSameWeek(normalizedWeek, currentWeek.value, { weekStartsOn: 1 })) return
    slideDirection.value = normalizedWeek.getTime() > currentWeek.value.getTime() ? 'left' : 'right'
    onHandleSetCurrentWeek(normalizedWeek)
  },
  { immediate: true },
)

const navigateToWeek = (week: Date) => {
  const normalizedWeek = startOfISOWeek(week)
  if (isSameWeek(normalizedWeek, currentWeek.value, { weekStartsOn: 1 })) return

  const isForward = normalizedWeek.getTime() > currentWeek.value.getTime()
  if (isForward && disableNextWeekInteraction.value) return
  if (!isForward && disableLastWeekInteraction.value) return

  slideDirection.value = isForward ? 'left' : 'right'
  onHandleSetCurrentWeek(normalizedWeek)
}

const incrementWeek = () => {
  emit('increment-week')
  navigateToWeek(addWeeks(currentWeek.value, 1))
}

const decrementWeek = () => {
  emit('decrement-week')
  navigateToWeek(subWeeks(currentWeek.value, 1))
}

const isWeekDayDisabled = (weekDay: Date) =>
  checkIfDateIsBeforeToday(weekDay) || !checkIfDateIsEnabled(weekDay, props.enabledDates)
const isTodayDate = (weekDay: Date) => isSameDay(weekDay, new Date())
</script>

<template>
  <section class="mb-6">
    <div class="max-w-[487px]">
      <div class="mb-3 flex w-full flex-row items-center">
        <p class="m-0 font-bold text-gray-800">{{ weekText }}</p>
        <div class="ml-auto flex flex-row justify-end gap-4">
          <FdsButtonIcon
            icon="arrowLeft"
            :disabled="disableLastWeekInteraction"
            :aria-label="t('FdsWeekCalendar.previousWeek')"
            @click="decrementWeek"
          />
          <FdsButtonIcon
            icon="arrowRight"
            :disabled="disableNextWeekInteraction"
            :aria-label="t('FdsWeekCalendar.nextWeek')"
            @click="incrementWeek"
          />
        </div>
      </div>

      <div class="relative h-[64px] overflow-hidden">
        <Transition
          :enter-active-class="transitionClasses.enterActiveClass"
          :leave-active-class="transitionClasses.leaveActiveClass"
          :enter-from-class="transitionClasses.enterFromClass"
          :leave-to-class="transitionClasses.leaveToClass"
        >
          <div :key="currentWeek.toISOString()" class="flex flex-row flex-nowrap items-center justify-start gap-x-px">
            <template v-for="(weekDay, _index) in visibleWeekDays" :key="`current-${weekDay.toISOString()}-${_index}`">
              <WeekDay
                :date="weekDay"
                :disabled="isWeekDayDisabled(weekDay)"
                :selected="!!selectedDay && isSameDay(weekDay, selectedDay)"
                :is-today="isTodayDate(weekDay)"
                @select-date="onHandleSelectedDay"
              />
            </template>
          </div>
        </Transition>
      </div>

      <div v-if="loading?.loading" class="mt-6 flex w-full flex-row items-center gap-2">
        <FdsSpinner />
        <p class="m-0 font-bold text-blue-700">{{ loading.label }}</p>
      </div>
    </div>
  </section>
</template>
