<template>
  <div>
    <div
      ref="root"
      :class="mergedGroupClass"
    >
      <input
        :id="inputId"
        :name="name"
        data-test="date-input"
        readonly
        autocomplete="off"
        :class="mergedInputClass"
        :required="required"
        :disabled="disabled"
        :value="displayValue"
        :aria-invalid="!!error"
        :aria-describedby="describedBy"
        :aria-expanded="isOpen"
        aria-haspopup="dialog"
        v-bind="attrs"
        placeholder="_"
        type="text"
        @click="onFieldClick"
      >

      <label
        v-if="label"
        :for="inputId"
        :class="mergedLabelClass"
      >
        {{ label }}
      </label>

      <div class="absolute right-3 top-1/2 flex -translate-y-1/2 items-center gap-1">
        <button
          v-if="showClear"
          type="button"
          data-test="clear"
          class="text-m-muted hover:text-black"
          aria-label="Effacer la date"
          @click.stop="emit('clear')"
        >
          <Icon
            icon="mdi:close"
            :width="16"
            :height="16"
          />
        </button>
        <Icon
          data-test="calendar-icon"
          icon="mdi:calendar-blank"
          :width="24"
          :height="24"
          :class="iconStateClass"
        />
      </div>

      <div
        v-if="isOpen"
        data-test="popover"
        role="dialog"
        class="absolute left-0 right-0 top-full z-20 box-border w-full rounded-b-md bg-white p-[10px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]"
      >
        <CalendarHeader
          :view-mode="viewMode"
          :current-month="currentMonth"
          :current-year="currentYear"
          @prev="goToPrev"
          @next="goToNext"
          @toggle-view="toggleView"
        />
        <slot
          v-if="viewMode === 'days'"
          :current-month="currentMonth"
          :current-year="currentYear"
          :close="closePopover"
        />
        <MonthPicker
          v-else
          :selected-month="currentMonth"
          @select="onSelectMonth"
        />
      </div>
    </div>

    <p
      v-if="hint || hasError || hasSuccess"
      :id="`${inputId}-describedby`"
      :class="[
        hasError ? 'text-m-danger' : hasSuccess ? 'text-m-success' : 'text-m-muted',
        'mt-1 ml-[2px] text-xs',
      ]"
    >
      {{ error || success || hint }}
    </p>
  </div>
</template>

<script setup lang="ts">
import {computed, ref, useAttrs, useId, watch} from 'vue'
import {Icon} from '@iconify/vue'
import {twMerge} from 'tailwind-merge'
import CalendarHeader from './CalendarHeader.vue'
import MonthPicker from './MonthPicker.vue'
import {useCalendarPopover} from '../composables/useCalendarPopover'
import {useCalendarView} from '../composables/useCalendarView'

defineOptions({name: 'MalioCalendarField', inheritAttrs: false})

const props = withDefaults(
  defineProps<{
    displayValue: string
    syncTo: string | null
    id?: string
    name?: string
    label?: string
    placeholder?: string
    required?: boolean
    disabled?: boolean
    readonly?: boolean
    hint?: string
    error?: string
    success?: string
    clearable?: boolean
    inputClass?: string
    labelClass?: string
    groupClass?: string
  }>(),
  {
    id: '',
    name: '',
    label: '',
    placeholder: 'JJ/MM/AAAA',
    required: false,
    disabled: false,
    readonly: false,
    hint: '',
    error: '',
    success: '',
    clearable: true,
    inputClass: '',
    labelClass: '',
    groupClass: '',
  },
)

const emit = defineEmits<{(e: 'clear' | 'close'): void}>()

const attrs = useAttrs()
const generatedId = useId()
const root = ref<HTMLElement | null>(null)

const {isOpen, viewMode, open, close: closePopover, toggleView} = useCalendarPopover(root)
const {currentMonth, currentYear, goToPrev, goToNext, selectMonth, syncToIso} = useCalendarView(viewMode)

const inputId = computed(() => props.id?.toString() || `malio-date-${generatedId}`)
const hasError = computed(() => !!props.error)
const hasSuccess = computed(() => !!props.success && !hasError.value)
const isFilled = computed(() => props.displayValue.length > 0)
const showClear = computed(() =>
  props.clearable && isFilled.value && !props.disabled && !props.readonly,
)
const describedBy = computed(() =>
  (props.hint || hasError.value || hasSuccess.value) ? `${inputId.value}-describedby` : undefined,
)

watch(isOpen, (value) => {
  if (!value) emit('close')
})

const onFieldClick = () => {
  if (props.disabled || props.readonly) return
  if (isOpen.value) {
    closePopover()
    return
  }
  syncToIso(props.syncTo)
  open()
}

watch(() => props.syncTo, (value) => {
  if (isOpen.value) syncToIso(value)
})

const onSelectMonth = (m: number) => {
  selectMonth(m)
  toggleView()
}

const mergedGroupClass = computed(() =>
  twMerge('relative flex h-12 w-full items-center', props.groupClass),
)

const mergedInputClass = computed(() =>
  twMerge(
    'floating-input peer min-h-[40px] w-full cursor-pointer rounded-md border bg-white py-1 pl-3 pr-10 text-lg outline-none transition-[padding] duration-150 placeholder:text-transparent',
    isFilled.value ? 'border-black' : 'border-m-muted',
    props.disabled ? 'cursor-not-allowed text-black/60 border-m-muted' : '',
    hasError.value
      ? 'border-m-danger'
      : hasSuccess.value
        ? 'border-m-success'
        : 'focus:border-m-primary',
    isOpen.value ? 'border-m-primary !py-[9px] !rounded-b-none' : '',
    props.inputClass,
  ),
)

const mergedLabelClass = computed(() =>
  twMerge(
    'floating-label absolute left-3 top-2 mt-[5px] inline-block origin-left font-medium text-sm transition-transform duration-150',
    (isFilled.value || isOpen.value) ? '-translate-y-[1.25rem] scale-90' : '',
    hasError.value
      ? 'text-m-danger'
      : hasSuccess.value
        ? 'text-m-success'
        : isOpen.value
          ? 'text-m-primary'
          : 'peer-placeholder-shown:text-m-muted text-black',
    props.labelClass,
  ),
)

const iconStateClass = computed(() => {
  if (hasError.value) return 'text-m-danger'
  if (hasSuccess.value) return 'text-m-success'
  if (isOpen.value) return 'text-m-primary'
  if (isFilled.value) return 'text-black'
  return 'text-m-muted'
})
</script>

<style scoped>
.floating-label {
  background: white;
  padding: 0 0.25rem;
}
</style>
