<template>
  <div class="cpDate" :class="dynamicClasses">
    <base-input-label v-if="label" v-bind-once="{ for: cpDateId }" :is-invalid="!isDateValid" :required="required">
      {{ capitalizedLabel }}
    </base-input-label>
    <div class="cpDate__inputs">
      <template v-for="(field, index) in orderedDateFields" :key="field">
        <input
          v-if="isDayField(field)"
          v-model="day"
          v-maska
          :autocomplete="autocompleteFields.day"
          class="cpDate__day"
          data-maska="##"
          :disabled="disabled"
          inputmode="numeric"
          maxlength="2"
          :placeholder="dayInputPlaceholder"
          :required="required"
        />
        <div v-else class="cpDate__month" :class="selectDynamicClass">
          <select
            :id="cpDateId"
            v-model="month"
            :autocomplete="autocompleteFields.month"
            :disabled="disabled"
            :required="required"
          >
            <option value>{{ monthInputPlaceholder }}</option>
            <option v-for="(monthItem, monthIndex) in months" :key="monthIndex" :value="monthItem.value">
              {{ monthItem.label }}
            </option>
          </select>
        </div>
        <div v-if="shouldShowDivider(index)" class="cpDate__divider" />
      </template>
      <div class="cpDate__divider" />
      <input
        v-model="year"
        v-maska
        :autocomplete="autocompleteFields.year"
        class="cpDate__year"
        data-maska="####"
        :disabled="disabled"
        inputmode="numeric"
        maxlength="4"
        :placeholder="yearInputPlaceholder"
        :required="required"
      />
    </div>
    <cp-transition-expand>
      <div v-if="advancedErrorMessage" class="cpDate__errorMessage">{{ advancedErrorMessage }}</div>
    </cp-transition-expand>
  </div>
</template>

<script setup lang="ts">
import { DateTime, Info } from 'luxon'
import { computed, ref, useId, watch } from 'vue'

import type { Sizes } from '@/constants'

import BaseInputLabel from '@/components/BaseInputLabel.vue'

import { HUMAN_MAX_AGE } from '@/constants'
import { capitalizeFirstLetter } from '@/helpers'

interface Emits {
  (e: 'update:modelValue', value: string): void
  (e: 'onValidation', value: boolean): void
}

interface InputsOptions {
  dayInputPlaceholder?: string
  monthInputPlaceholder?: string
  yearInputPlaceholder?: string
}
type DateField = 'day' | 'month'

interface Props {
  allowEmpty?: boolean
  autocompleteBirthday?: boolean
  disabled?: boolean
  displayErrorMessage?: boolean
  errorMessage?: string
  inputsOptions?: InputsOptions
  isInvalid?: boolean
  label?: string
  locale?: string
  maxDate?: string | Date
  minDate?: string | Date
  modelValue?: string | Date
  required?: boolean
  size?: Sizes
}

const props = withDefaults(defineProps<Props>(), {
  modelValue: '',
  minDate: () => DateTime.now().minus({ year: HUMAN_MAX_AGE }).toISO(),
  maxDate: () => DateTime.now().toISO(),
  label: '',
  required: false,
  allowEmpty: false,
  disabled: false,
  isInvalid: false,
  errorMessage: '',
  displayErrorMessage: true,
  autocompleteBirthday: false,
  locale: () => navigator.language,
  inputsOptions: () => ({}),
  size: 'md',
})

const emit = defineEmits<Emits>()

const initDateToken = (token: 'day' | 'month' | 'year'): string => {
  if (DateTime.fromISO(props.modelValue).invalid) return ''

  return DateTime.fromISO(props.modelValue)[token]
}

const day = ref(initDateToken('day'))
const month = ref(initDateToken('month'))
const year = ref(initDateToken('year'))

const capitalizedLabel = computed(() => capitalizeFirstLetter(props.label))

const cpDateId = useId()

const minYear = computed(() => isoMinDate.value.year)

const maxYear = computed(() => isoMaxDate.value.year)

const monthMaxDay = computed(() => {
  const date = DateTime.fromObject({
    year: year.value && year.value.length <= 4 ? Number(year.value) : DateTime.now().year,
    month: month.value ? Number(month.value) : DateTime.now().month,
    day: 1,
  })

  return date.daysInMonth
})

const months = computed(() => {
  const rawLocalizedMonths = Info.months('long', { locale: props.locale })

  return rawLocalizedMonths.map((month: string, index: number) => {
    return {
      label: capitalizeFirstLetter(month),
      value: index + 1,
    }
  })
})

const isoDate = computed(() => {
  return DateTime.fromObject({
    year: Number(year.value),
    month: Number(month.value),
    day: Number(day.value),
  }).toISODate()
})

const isoMinDate = computed(() => {
  return DateTime.fromISO(props.minDate)
})

const isoMaxDate = computed(() => {
  return DateTime.fromISO(props.maxDate)
})

const isDateAfterMinDate = computed(() => {
  return isoDate.value >= isoMinDate.value.toISODate()
})

const isDateBeforeMaxDate = computed(() => {
  return isoDate.value <= isoMaxDate.value.toISODate()
})

const areInputsEmpty = computed(() => {
  return day.value === '' && month.value === '' && year.value === ''
})

const isDateValid = computed(() => {
  if (areInputsEmpty.value && (!props.required || props.allowEmpty))  return true

  return (
    !props.isInvalid &&
    isDayValid.value &&
    isMonthValid.value &&
    isYearValid.value &&
    isDateBeforeMaxDate.value &&
    isDateAfterMinDate.value
  )
})

const isDayValid = computed(() => {
  return Number(day.value) >= 1 && Number(day.value) <= monthMaxDay.value
})

const isMonthValid = computed(() => {
  return !!month.value
})

const isYearValid = computed(() => {
  return Number(year.value) >= minYear.value && Number(year.value) <= maxYear.value
})

const advancedErrorMessage = computed(() => {
  if (isDateValid.value || !props.displayErrorMessage) return ''

  if (props.errorMessage) return props.errorMessage

  if (!isMonthValid.value) {
    return 'Month is required.'
  }

  if (!isDayValid.value) {
    return `Day must be in the range 1 – ${monthMaxDay.value}.`
  }

  if (!isYearValid.value) {
    return `Year must be in the range ${minYear.value} – ${maxYear.value}.`
  }

  if (!isDateBeforeMaxDate.value) {
    const formattedMaxDate = isoMaxDate.value.toFormat('DDD')
    return `The date can't be after ${formattedMaxDate}.`
  }

  if (!isDateAfterMinDate.value) {
    const formattedMinDate = isoMinDate.value.toFormat('DDD')
    return `The date can't be before ${formattedMinDate}.`
  }

  return ''
})

const dynamicClasses = computed(() => {
  return [
    `cpDate--${props.size}`,
    {
      'cpDate--isInvalid': !isDateValid.value,
      'cpDate--isDisabled': props.disabled,
    },
  ]
})

const selectDynamicClass = computed(() => {
  return {
    'cpDate__month--isEmpty': !month.value,
  }
})

const autocompleteFields = computed(() => {
  if (!props.autocompleteBirthday) {
    return { day: 'off', month: 'off', year: 'off' }
  }

  return {
    day: 'bday-day',
    month: 'bday-month',
    year: 'bday-year',
  }
})

const dayInputPlaceholder = computed(() => {
  return props.inputsOptions?.dayInputPlaceholder || 'DD'
})

const monthInputPlaceholder = computed(() => {
  return props.inputsOptions?.monthInputPlaceholder || 'Months'
})

const isDayFirst = computed(() => {
  const parts = new Intl.DateTimeFormat(props.locale, {
    day: '2-digit',
    month: '2-digit',
  }).formatToParts(new Date(2024, 0, 31))

  const dayIndex = parts.findIndex((part) => part.type === 'day')
  const monthIndex = parts.findIndex((part) => part.type === 'month')

  if (dayIndex === -1 || monthIndex === -1) return false

  return dayIndex < monthIndex
})

const orderedDateFields = computed<DateField[]>(() => {
  return isDayFirst.value ? ['day', 'month'] : ['month', 'day']
})

const yearInputPlaceholder = computed(() => {
  return props.inputsOptions?.yearInputPlaceholder || 'YYYY'
})

const handleUpdate = (): void => {
  emit('update:modelValue', isoDate.value)
  emit('onValidation', isDateValid.value)
}

const isDayField = (field: DateField): boolean => field === 'day'

const shouldShowDivider = (index: number): boolean => index < orderedDateFields.value.length - 1

watch(day, handleUpdate)
watch(month, handleUpdate)
watch(year, handleUpdate)
</script>

<style lang="scss">
// Reset default input style
.cpDate {
  input,
  select {
    background-color: transparent;
    color: var(--cp-text-primary);
  }

  // Hide arrows from number input
  input::-webkit-outer-spin-button,
  input::-webkit-inner-spin-button {
    margin: 0;
    appearance: none;
  }

  input[type='number'] {
    appearance: textfield;
  }

  // Remove select style
  select {
    border: none;
    appearance: none;
    outline: none;
  }

  input::placeholder,
  &__month--isEmpty {
    color: var(--cp-text-placeholder);
  }

  &__inputs {
    display: flex;
    overflow: hidden;
    border-radius: var(--cp-radius-md-lg);
    background-color: var(--cp-background-primary);
    box-shadow:
      var(--cp-shadows-3xs),
      0 0 0 var(--cp-dimensions-0_25) var(--cp-border-soft);

    &:hover {
      border-color: var(--cp-border-soft-hover);
    }

    &:focus-within {
      outline: var(--cp-dimensions-0_5) solid var(--cp-border-accent-solid);
    }
  }

  &__inputs > *:not(.cpDate__divider) {
    line-height: var(--cp-line-height-md);
  }

  &__month,
  &__day,
  &__year {
    min-width: 0;
  }

  &__month {
    position: relative;
    flex: 1 1 40%;

    &::after {
      position: absolute;
      top: 50%;
      right: var(--cp-dimensions-3);
      width: var(--cp-dimensions-5);
      height: var(--cp-dimensions-5);
      background-color: var(--cp-text-secondary);
      content: '';
      mask-image: url('@/assets/images/icons/chevron-down-icon.svg');
      mask-repeat: no-repeat;
      mask-size: cover;
      pointer-events: none;
      transform: translateY(-50%);
    }

    select {
      @extend %u-text-ellipsis;

      width: 100%;
      color: inherit;
      cursor: pointer;
      line-height: var(--cp-line-height-md);

      &:focus-visible {
        text-decoration: underline;
      }
    }

    select > option:not(:disabled) {
      color: var(--cp-text-primary);
    }
  }

  &__day,
  &__year {
    width: 100%;
    flex: 0 0 30%;
  }


  &--lg {
    .cpDate__day,
    .cpDate__year {
      padding: var(--cp-spacing-lg) var(--cp-spacing-xl);
      font-size: var(--cp-text-size-md);
    }

    .cpDate__month select {
      padding: var(--cp-spacing-lg) var(--cp-dimensions-8) var(--cp-spacing-lg) var(--cp-spacing-xl);
      font-size: var(--cp-text-size-md);
    }
  }

  &--md {
    .cpDate__day,
    .cpDate__year {
      padding: var(--cp-spacing-md) var(--cp-spacing-xl);
      font-size: var(--cp-text-size-sm);
    }

    .cpDate__month select {
      padding: var(--cp-spacing-md) calc(var(--cp-dimensions-1) * 7) var(--cp-spacing-md) var(--cp-spacing-lg);
      font-size: var(--cp-text-size-sm);
    }
  }

  &--sm {
    .cpDate__day,
    .cpDate__year {
      padding: var(--cp-spacing-sm) var(--cp-spacing-lg);
      font-size: var(--cp-text-size-sm);
    }

    .cpDate__month select {
      padding: var(--cp-spacing-sm) var(--cp-dimensions-8) var(--cp-spacing-sm) var(--cp-spacing-lg);
      font-size: var(--cp-text-size-sm);
    }
  }

  &__divider {
    width: var(--cp-dimensions-0_25);
    flex-shrink: 0;
    background-color: var(--cp-border-soft);
  }

  &--isDisabled {
    .cpDate__inputs {
      border-color: var(--cp-border-disabled);
      background: var(--cp-background-disabled);
    }

    .cpDate__inputs *,
    input::placeholder {
      color: var(--cp-text-disabled) !important;
      cursor: not-allowed !important;
    }

    .cpDate__month select:disabled {
      color: var(--cp-text-disabled) !important;
      opacity: 1;
      -webkit-text-fill-color: var(--cp-text-disabled) !important;
    }

    .cpDate__month::after {
      opacity: 0.4;
    }
  }

  &--isInvalid {
    .cpDate__inputs,
    .cpDate__inputs:hover {
      box-shadow: 0 0 0 var(--cp-dimensions-0_25) var(--cp-border-error-solid);
    }

    .cpDate__inputs:focus-within {
      box-shadow: 0 0 0 var(--cp-dimensions-0_25) color-mix(in srgb, #{var(--cp-border-error-solid)} 40%, transparent);
      outline: var(--cp-dimensions-0_5) solid var(--cp-border-error-solid);
    }

    .cpDate__divider {
      background-color: var(--cp-border-error-solid);
    }
  }

  &__errorMessage {
    margin-top: var(--cp-spacing-md);
    color: var(--cp-text-error-primary);
    font-size: var(--cp-text-size-sm);
    line-height: var(--cp-line-height-md);
  }
}
</style>
