import {useControlledState} from '@react-stately/utils';
import {HTMLAttributes, useCallback, useState} from 'react';
import {
  CalendarDate,
  DateValue,
  getLocalTimeZone,
  isSameDay,
  now,
  toCalendarDate,
  toZoned,
  ZonedDateTime,
} from '@internationalized/date';
import {useBaseDatePickerState} from '../use-base-date-picker-state';

const Now = now(getLocalTimeZone());

export type Granularity = 'day' | 'minute';

export type DatePickerState = BaseDatePickerState;

export interface BaseDatePickerState<T = ZonedDateTime, P = boolean> {
  timeZone: string;
  granularity: Granularity;
  selectedValue: T;
  setSelectedValue: (value: T) => void;
  calendarIsOpen: boolean;
  setCalendarIsOpen: (isOpen: boolean) => void;
  calendarDates: CalendarDate[];
  setCalendarDates: (dates: CalendarDate[]) => void;
  dayIsActive: (day: CalendarDate) => boolean;
  dayIsHighlighted: (day: CalendarDate) => boolean;
  dayIsRangeStart: (day: CalendarDate) => boolean;
  dayIsRangeEnd: (day: CalendarDate) => boolean;
  isPlaceholder: P;
  setIsPlaceholder: (value: P) => void;
  min?: ZonedDateTime;
  max?: ZonedDateTime;
  closeDialogOnSelection: boolean;
  getCellProps: (
    date: CalendarDate,
    isSameMonth: boolean
  ) => HTMLAttributes<HTMLElement>;
}

export interface DatePickerValueProps<T> {
  value?: T | null | '';
  defaultValue?: T | null;
  onChange?: (value: T) => void;
  min?: DateValue;
  max?: DateValue;
  granularity?: Granularity;
  closeDialogOnSelection?: boolean;
}
export function useDatePickerState(
  props: DatePickerValueProps<ZonedDateTime>
): BaseDatePickerState {
  const [isPlaceholder, setIsPlaceholder] = useState(
    !props.value && !props.defaultValue
  );

  const [selectedValue, setSelectedStateValue] = useControlledState(
    props.value || Now,
    props.defaultValue || Now,
    props.onChange
  );

  const {
    min,
    max,
    granularity,
    timeZone,
    calendarIsOpen,
    setCalendarIsOpen,
    closeDialogOnSelection,
  } = useBaseDatePickerState(selectedValue, props);

  const [calendarDates, setCalendarDates] = useState<CalendarDate[]>(() => {
    return [toCalendarDate(selectedValue)];
  });

  const setSelectedValue = useCallback(
    (newValue: DateValue) => {
      if (min && newValue.compare(min) < 0) {
        newValue = min;
      } else if (max && newValue.compare(max) > 0) {
        newValue = max;
      }

      // preserve time
      const value = selectedValue
        ? selectedValue.set(newValue)
        : toZoned(newValue, getLocalTimeZone());
      setSelectedStateValue(value);
      setCalendarDates([toCalendarDate(value)]);
      setIsPlaceholder(false);
    },
    [setSelectedStateValue, min, max, selectedValue]
  );

  const dayIsActive = useCallback(
    (day: DateValue) => !isPlaceholder && isSameDay(selectedValue, day),
    [selectedValue, isPlaceholder]
  );

  const getCellProps = useCallback(
    (date: DateValue): HTMLAttributes<HTMLElement> => {
      return {
        onClick: () => {
          setSelectedValue?.(date);
          if (closeDialogOnSelection) {
            setCalendarIsOpen?.(false);
          }
        },
      };
    },
    [setSelectedValue, setCalendarIsOpen, closeDialogOnSelection]
  );

  return {
    selectedValue,
    setSelectedValue,
    calendarIsOpen,
    setCalendarIsOpen,
    dayIsActive,
    dayIsHighlighted: () => false,
    dayIsRangeStart: () => false,
    dayIsRangeEnd: () => false,
    getCellProps,
    calendarDates,
    setCalendarDates,
    isPlaceholder,
    setIsPlaceholder,
    min,
    max,
    granularity,
    timeZone,
    closeDialogOnSelection,
  };
}
