/*
  * Adapted from https://github.com/melt-ui/melt-ui/blob/develop/src/lib/builders/range-calendar/create.ts
*/

import type { DateValue } from '@internationalized/date'
import type { Ref } from 'vue'
import type { Matcher } from '@/date'
import { isSameDay } from '@internationalized/date'
import { computed } from 'vue'
import { areAllDaysBetweenValid, getDaysBetween, isBefore, isBetween } from '@/date'

export type UseRangeCalendarProps = {
  start: Ref<DateValue | undefined>
  end: Ref<DateValue | undefined>
  isDateDisabled: Matcher
  isDateUnavailable: Matcher
  isDateHighlightable?: Matcher
  focusedValue: Ref<DateValue | undefined>
  allowNonContiguousRanges: Ref<boolean>
  fixedDate: Ref<'start' | 'end' | undefined>
  maximumDays?: Ref<number | undefined>
}

export function useRangeCalendarState(props: UseRangeCalendarProps) {
  const isStartInvalid = computed(() => {
    if (!props.start.value)
      return false
    if (props.isDateDisabled(props.start.value))
      return true
    return false
  })

  const isEndInvalid = computed(() => {
    if (!props.end.value)
      return false
    if (props.isDateDisabled(props.end.value))
      return true
    return false
  })

  const isInvalid = computed(
    () => {
      if (isStartInvalid.value || isEndInvalid.value)
        return true
      if (props.start.value && props.end.value && isBefore(props.end.value, props.start.value))
        return true
      return false
    },
  )

  const isSelectionStart = (date: DateValue) => {
    if (!props.start.value)
      return false
    return isSameDay(props.start.value, date)
  }

  const isSelectionEnd = (date: DateValue) => {
    if (!props.end.value)
      return false
    return isSameDay(props.end.value, date)
  }

  const isSelected = (date: DateValue) => {
    if (props.start.value && isSameDay(props.start.value, date))
      return true
    if (props.end.value && isSameDay(props.end.value, date))
      return true
    if (props.end.value && props.start.value)
      return isBetween(date, props.start.value, props.end.value)

    return false
  }

  const rangeIsDateDisabled = (date: DateValue) => {
    if (props.isDateDisabled(date))
      return true

    if (props.maximumDays?.value) {
      if (props.start.value && props.end.value) {
        if (props.fixedDate.value) {
          const diff = getDaysBetween(props.start.value, props.end.value).length
          if (diff <= props.maximumDays.value) {
            const daysLeft = props.maximumDays.value - diff - 1
            const startLimit = props.start.value.subtract({ days: daysLeft })
            const endLimit = props.end.value.add({ days: daysLeft })
            return !isBetween(date, startLimit, endLimit)
          }
        }
        return false
      }
      if (props.start.value) {
        const maxDate = props.start.value.add({ days: props.maximumDays.value })
        const minDate = props.start.value.subtract({ days: props.maximumDays.value })
        return !isBetween(date, minDate, maxDate)
      }
    }

    return false
  }

  const isDateHighlightable = (date: DateValue) => {
    if (props.isDateHighlightable?.(date))
      return true
    return false
  }

  const highlightedRange = computed(() => {
    if (props.start.value && props.end.value && !props.fixedDate.value)
      return null
    if (!props.start.value || !props.focusedValue.value)
      return null

    const isStartBeforeFocused = isBefore(props.start.value, props.focusedValue.value)
    const start = isStartBeforeFocused ? props.start.value : props.focusedValue.value
    const end = isStartBeforeFocused ? props.focusedValue.value : props.start.value

    if (isSameDay(start, end)) {
      return {
        start,
        end,
      }
    }

    if (props.maximumDays?.value && !props.end.value) {
      const maximumDays = props.maximumDays.value
      const anchor = props.start.value
      const focused = props.focusedValue.value

      if (!isBefore(focused, anchor))
        return { start: anchor, end: anchor.add({ days: maximumDays - 1 }) }

      return { start: anchor.subtract({ days: maximumDays - 1 }), end: anchor }
    }

    const isValid = areAllDaysBetweenValid(start, end, props.allowNonContiguousRanges.value ? () => false : props.isDateUnavailable, rangeIsDateDisabled, props.isDateHighlightable)
    if (isValid) {
      return {
        start,
        end,
      }
    }
    return null
  })

  const isHighlightedStart = (date: DateValue) => {
    if (!highlightedRange.value || !highlightedRange.value.start)
      return false
    return isSameDay(highlightedRange.value.start, date)
  }

  const isHighlightedEnd = (date: DateValue) => {
    if (!highlightedRange.value || !highlightedRange.value.end)
      return false
    return isSameDay(highlightedRange.value.end, date)
  }

  const hasSelectedDate = computed(() => {
    return !!(props.start.value || props.end.value)
  })

  const isStartDateDisabled = computed(() => {
    return !!(props.start.value && props.isDateDisabled(props.start.value))
  })

  const isEndDateDisabled = computed(() => {
    return !!(props.end.value && props.isDateDisabled(props.end.value))
  })

  const isSelectedDisabled = computed(() => {
    const hasStart = !!props.start.value
    const hasEnd = !!props.end.value
    if (!hasStart && !hasEnd)
      return false
    if (hasStart && hasEnd)
      return isStartDateDisabled.value && isEndDateDisabled.value
    return (hasStart && isStartDateDisabled.value) || (hasEnd && isEndDateDisabled.value)
  })

  const selectedFocusableDate = computed(() => {
    if (props.start.value && !isStartDateDisabled.value)
      return props.start.value
    if (props.end.value && !isEndDateDisabled.value)
      return props.end.value
    return undefined
  })

  return {
    isInvalid,
    isSelected,
    isDateHighlightable,
    highlightedRange,
    isSelectionStart,
    isSelectionEnd,
    isHighlightedStart,
    isHighlightedEnd,
    isDateDisabled: rangeIsDateDisabled,
    hasSelectedDate,
    isSelectedDisabled,
    selectedFocusableDate,
  }
}
