<script setup lang="ts">
import { DateFormatter, getLocalTimeZone } from '@internationalized/date';
import type { DateRange, DateValue } from 'reka-ui';
import { computed, defineAsyncComponent, ref } from 'vue';
import { cn } from '@/lib/utils';
import { UiButton } from '@/components/UiButton';
import { UiIcon } from '@/components/UiIcon';
import {
  UiPopover,
  UiPopoverContent,
  UiPopoverTrigger,
} from '@/components/UiPopover';
import type { UiDatePickerProps } from './types';

defineOptions({
  name: 'UiDatePicker',
});

// Lazy-load calendar components to reduce bundle size.
// Using v-if/v-else ensures only the needed component is loaded,
// while maintaining full TypeScript type safety.
const UiCalendar = defineAsyncComponent(() =>
  import('@/components/UiCalendar').then((m) => m.UiCalendar),
);
const UiRangeCalendar = defineAsyncComponent(() =>
  import('@/components/UiRangeCalendar').then((m) => m.UiRangeCalendar),
);

const props = withDefaults(defineProps<UiDatePickerProps>(), {
  mode: 'single',
  disabled: false,
  readonly: false,
  locale: 'en',
  weekStartsOn: 0,
  numberOfMonths: 1,
  pagedNavigation: false,
  weekdayFormat: 'narrow',
  fixedWeeks: false,
  preventDeselect: false,
  closeOnSelect: true,
  dateFormat: () => ({ dateStyle: 'long' }),
  rangeSeparator: ' - ',
});

const modelValue = defineModel<UiDatePickerProps['modelValue']>();
const open = defineModel<boolean>('open');

// Internal state for uncontrolled mode (when defaultValue is provided without v-model)
const internalValue = ref<UiDatePickerProps['modelValue']>(props.defaultValue);
const internalOpen = ref(false);

// Uncontrolled mode: defaultValue provided, use internal state
// Controlled mode: no defaultValue, use v-model
const isUncontrolled = props.defaultValue !== undefined;

const effectiveValue = computed(() =>
  isUncontrolled ? internalValue.value : modelValue.value,
);
const effectiveOpen = computed({
  get: () => open.value ?? internalOpen.value,
  set: (value: boolean) => {
    open.value = value;
    internalOpen.value = value;
  },
});

const isDateRange = (value: unknown): value is DateRange => {
  return (
    value !== null &&
    typeof value === 'object' &&
    'start' in value &&
    'end' in value
  );
};

// Type-safe computed values for each mode (avoids type assertions in template)
// Cast needed because TypeScript can't narrow the DateValue | DateRange union
const singleValue = computed((): DateValue | undefined => {
  const value = effectiveValue.value as DateValue | DateRange | undefined;
  return props.mode === 'single' && value && !isDateRange(value)
    ? value
    : undefined;
});

const rangeValue = computed((): DateRange | undefined => {
  const value = effectiveValue.value as DateValue | DateRange | undefined;
  return props.mode === 'range' && value && isDateRange(value)
    ? value
    : undefined;
});

const dateFormatter = computed(
  () => new DateFormatter(props.locale, props.dateFormat),
);

const computedPlaceholder = computed(() => {
  if (props.placeholder) return props.placeholder;
  return props.mode === 'range' ? 'Pick a date range' : 'Pick a date';
});

const displayValue = computed(() => {
  if (props.mode === 'range') {
    const range = rangeValue.value;
    if (!range?.start) return computedPlaceholder.value;

    const startStr = dateFormatter.value.format(
      range.start.toDate(getLocalTimeZone()),
    );
    if (!range.end) return startStr;

    const endStr = dateFormatter.value.format(
      range.end.toDate(getLocalTimeZone()),
    );
    return `${startStr}${props.rangeSeparator}${endStr}`;
  }

  const single = singleValue.value;
  if (!single) return computedPlaceholder.value;
  return dateFormatter.value.format(single.toDate(getLocalTimeZone()));
});

const updateValue = (value: UiDatePickerProps['modelValue']) => {
  if (isUncontrolled) {
    internalValue.value = value;
  } else {
    modelValue.value = value;
  }
};

const handleSingleDateUpdate = (value: DateValue | DateValue[] | undefined) => {
  // Handle array case (shouldn't happen in single mode, but type-safe)
  const newValue = Array.isArray(value) ? value[0] : value;
  updateValue(newValue);

  if (props.closeOnSelect) {
    effectiveOpen.value = false;
  }
};

const handleRangeDateUpdate = (value: DateRange | undefined) => {
  updateValue(value);
  // Close only when both start and end dates are selected
  if (props.closeOnSelect && value?.start && value?.end) {
    effectiveOpen.value = false;
  }
};
</script>

<template>
  <UiPopover v-model:open="effectiveOpen">
    <UiPopoverTrigger as-child>
      <UiButton
        variant="outline"
        :class="
          cn(
            props.mode === 'range' ? 'w-[300px]' : 'w-[240px]',
            'justify-start text-left font-normal bg-input-background hover:bg-input-background',
            !effectiveValue && 'text-muted-foreground',
            props.class,
          )
        "
        :disabled="props.disabled"
        :aria-label="props.ariaLabel"
      >
        <UiIcon name="calendar" :size="16" class="mr-2 shrink-0" />
        <span class="truncate">{{ displayValue }}</span>
      </UiButton>
    </UiPopoverTrigger>
    <UiPopoverContent class="w-auto p-0" align="start">
      <Suspense>
        <UiCalendar
          v-if="props.mode === 'single'"
          :model-value="singleValue"
          :min-value="props.minValue"
          :max-value="props.maxValue"
          :disabled="props.disabled"
          :readonly="props.readonly"
          :locale="props.locale"
          :week-starts-on="props.weekStartsOn"
          :number-of-months="props.numberOfMonths"
          :weekday-format="props.weekdayFormat"
          :fixed-weeks="props.fixedWeeks"
          :layout="props.layout"
          :prevent-deselect="props.preventDeselect"
          :calendar-label="props.calendarLabel"
          :is-date-disabled="props.isDateDisabled"
          :is-date-unavailable="props.isDateUnavailable"
          class="rounded-none border-0 bg-transparent shadow-none"
          initial-focus
          @update:model-value="handleSingleDateUpdate"
        />
        <UiRangeCalendar
          v-else
          :model-value="rangeValue"
          :min-value="props.minValue"
          :max-value="props.maxValue"
          :disabled="props.disabled"
          :readonly="props.readonly"
          :locale="props.locale"
          :week-starts-on="props.weekStartsOn"
          :number-of-months="props.numberOfMonths"
          :paged-navigation="props.pagedNavigation"
          :weekday-format="props.weekdayFormat"
          :fixed-weeks="props.fixedWeeks"
          :prevent-deselect="props.preventDeselect"
          :calendar-label="props.calendarLabel"
          :is-date-disabled="props.isDateDisabled"
          :is-date-unavailable="props.isDateUnavailable"
          class="rounded-none border-0 bg-transparent shadow-none"
          initial-focus
          @update:model-value="handleRangeDateUpdate"
        />
      </Suspense>
    </UiPopoverContent>
  </UiPopover>
</template>
