<template>
  <div ref="root">
    <div :class="mergedGroupClass">
      <input
        :id="inputId"
        :name="name"
        data-test="time-field"
        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 }}<MalioRequiredMark v-if="required" />
      </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-m-primary"
          aria-label="Effacer l'heure"
          @click.stop="onClear"
        >
          <Icon icon="mdi:close" :width="16" :height="16" />
        </button>
        <Icon
          data-test="clock-icon"
          icon="mdi:clock-outline"
          :width="24"
          :height="24"
          :class="iconStateClass"
        />
      </div>

      <!-- Mode overlay (par défaut) : popover absolu au-dessus du contenu suivant. -->
      <div
        v-if="isOpen && !staticPopover"
        data-test="popover"
        role="dialog"
        class="absolute left-0 right-0 top-full z-20 box-border w-full bg-white shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]"
      >
        <TimeWheels
          :model-value="wheelsValue"
          @update:model-value="onWheelChange"
        />
      </div>
    </div>

    <!-- Mode statique : molette en flux (hors du groupe à hauteur fixe) → le
         conteneur parent (ex. popover du DateTime) grandit pour l'englober. -->
    <div
      v-if="isOpen && staticPopover"
      data-test="popover"
      role="dialog"
      class="relative mt-4 w-full bg-white shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]"
    >
      <TimeWheels
        :model-value="wheelsValue"
        @update:model-value="onWheelChange"
      />
    </div>

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

<script setup lang="ts">
import {computed, onBeforeUnmount, onMounted, ref, useAttrs, useId} from 'vue'
import {Icon} from '@iconify/vue'
import {twMerge} from 'tailwind-merge'
import MalioRequiredMark from '../shared/RequiredMark.vue'
import TimeWheels from './internal/TimeWheels.vue'

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

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

const emit = defineEmits<{(e: 'update:modelValue', value: string | null): void}>()

const attrs = useAttrs()
const generatedId = useId()
const root = ref<HTMLElement | null>(null)
const isOpen = ref(false)
const localValue = ref<string | null>(null)

const isControlled = computed(() => props.modelValue !== undefined)
const currentValue = computed(() => (isControlled.value ? props.modelValue : localValue.value))

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

const commit = (value: string | null) => {
  if (!isControlled.value) localValue.value = value
  emit('update:modelValue', value)
}

const onWheelChange = (value: string) => commit(value)

const onClear = () => {
  commit(null)
}

const onFieldClick = () => {
  if (props.disabled || props.readonly) return
  isOpen.value = !isOpen.value
}

const onMouseDown = (event: MouseEvent) => {
  if (!isOpen.value || !root.value) return
  if (!root.value.contains(event.target as Node)) isOpen.value = false
}

onMounted(() => document.addEventListener('mousedown', onMouseDown))
onBeforeUnmount(() => document.removeEventListener('mousedown', onMouseDown))

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 border-m-muted text-black/60' : '',
    hasError.value
      ? 'border-m-danger'
      : hasSuccess.value
        ? 'border-m-success'
        : 'focus:border-m-primary',
    isOpen.value ? 'border-m-primary !rounded-b-none !py-[9px]' : '',
    props.inputClass,
  ),
)

const mergedLabelClass = computed(() =>
  twMerge(
    'floating-label absolute left-3 top-2 mt-[5px] inline-block origin-left text-sm font-medium 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'
          : 'text-black peer-placeholder-shown:text-m-muted',
    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>
