// Tremor Date Picker [v1.0.4]

"use client";

import * as React from "react";
import { Time } from "@internationalized/date";
import * as PopoverPrimitives from "@radix-ui/react-popover";
import {
  AriaTimeFieldProps,
  TimeValue,
  useDateSegment,
  useTimeField,
} from "@react-aria/datepicker";
import {
  useTimeFieldState,
  type DateFieldState,
  type DateSegment,
} from "@react-stately/datepicker";
import { RiCalendar2Fill, RiSubtractFill } from "@remixicon/react";
import { format, type Locale } from "date-fns";
import { enUS } from "date-fns/locale";
import { tv, VariantProps } from "tailwind-variants";

import { cx, focusInput, focusRing, hasErrorInput } from "../../lib/utils";

import { Button } from "../Button";
import { Calendar as CalendarPrimitive, type Matcher } from "../Calendar";

//#region TimeInput
// ============================================================================

const isBrowserLocaleClockType24h = () => {
  const language =
    typeof window !== "undefined" ? window.navigator.language : "en-US";

  const hr = new Intl.DateTimeFormat(language, {
    hour: "numeric",
  }).format();

  return Number.isInteger(Number(hr));
};

type TimeSegmentProps = {
  segment: DateSegment;
  state: DateFieldState;
};

const TimeSegment = ({ segment, state }: TimeSegmentProps) => {
  const ref = React.useRef<HTMLDivElement>(null);

  const { segmentProps } = useDateSegment(segment, state, ref);

  const isColon = segment.type === "literal" && segment.text === ":";
  const isSpace = segment.type === "literal" && segment.text === " ";

  const isDecorator = isColon || isSpace;

  return (
    <div
      {...segmentProps}
      ref={ref}
      className={cx(
        // base
        "onvo-relative onvo-block onvo-w-full onvo-appearance-none onvo-rounded-md onvo-border onvo-px-2.5 onvo-py-1.5 onvo-text-left onvo-uppercase onvo-tabular-nums onvo-shadow-sm onvo-outline-none onvo-transition sm:onvo-text-sm",
        // border color
        "onvo-border-gray-300 dark:onvo-border-gray-800",
        // text color
        "onvo-text-gray-900 dark:onvo-text-gray-50",
        // background color
        "onvo-bg-white dark:onvo-bg-gray-950",
        // focus
        focusInput,
        // invalid (optional)
        "invalid:onvo-border-red-500 invalid:onvo-ring-2 invalid:onvo-ring-red-200 group-aria-[invalid=true]/time-input:onvo-border-red-500 group-aria-[invalid=true]/time-input:onvo-ring-2 group-aria-[invalid=true]/time-input:onvo-ring-red-200 group-aria-[invalid=true]/time-input:dark:onvo-ring-red-400/20",
        {
          "!onvo-w-fit onvo-border-none onvo-bg-transparent onvo-px-0 onvo-text-gray-400 onvo-shadow-none":
            isDecorator,
          hidden: isSpace,
          "onvo-border-gray-300 onvo-bg-gray-100 onvo-text-gray-400 dark:onvo-border-gray-700 dark:onvo-bg-gray-800 dark:onvo-text-gray-500":
            state.isDisabled,
          "!onvo-bg-transparent !onvo-text-gray-400": !segment.isEditable,
        },
      )}
    >
      <span
        aria-hidden="true"
        className={cx(
          "onvo-pointer-events-none onvo-block w-full onvo-text-left onvo-text-gray-700 sm:onvo-text-sm",
          {
            hidden: !segment.isPlaceholder,
            "onvo-h-0": !segment.isPlaceholder,
          },
        )}
      >
        {segment.placeholder}
      </span>
      {segment.isPlaceholder ? "" : segment.text}
    </div>
  );
};

type TimeInputProps = Omit<
  AriaTimeFieldProps<TimeValue>,
  "label" | "shouldForceLeadingZeros" | "description" | "errorMessage"
>;

const TimeInput = React.forwardRef<HTMLDivElement, TimeInputProps>(
  ({ hourCycle, ...props }: TimeInputProps, ref) => {
    const innerRef = React.useRef<HTMLDivElement>(null);

    React.useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(
      ref,
      () => innerRef?.current,
    );

    const locale = window !== undefined ? window.navigator.language : "en-US";

    const state = useTimeFieldState({
      hourCycle: hourCycle,
      locale: locale,
      shouldForceLeadingZeros: true,
      autoFocus: true,
      ...props,
    });

    const { fieldProps } = useTimeField(
      {
        ...props,
        hourCycle: hourCycle,
        shouldForceLeadingZeros: true,
      },
      state,
      innerRef,
    );

    return (
      <div
        {...fieldProps}
        ref={innerRef}
        className="onvo-group/time-input onvo-inline-flex onvo-w-full onvo-gap-x-2"
      >
        {state.segments.map((segment, i) => (
          <TimeSegment key={i} segment={segment} state={state} />
        ))}
      </div>
    );
  },
);
TimeInput.displayName = "TimeInput";

//#region Trigger
// ============================================================================

const triggerStyles = tv({
  base: [
    // base
    "onvo-peer onvo-flex onvo-w-full onvo-cursor-pointer onvo-appearance-none onvo-items-center onvo-gap-x-2 onvo-truncate onvo-rounded-md onvo-border onvo-px-3 onvo-py-1.5 onvo-shadow-sm onvo-outline-none onvo-transition-all sm:onvo-text-sm",
    // background color
    "onvo-bg-white dark:onvo-bg-gray-950",
    // border color
    "onvo-border-gray-200 dark:onvo-border-gray-800",
    // text color
    "onvo-text-gray-900 dark:onvo-text-gray-50",
    // placeholder color
    "onvo-placeholder-gray-400 dark:onvo-placeholder-gray-500",
    // hover
    "hover:onvo-bg-gray-50 hover:dark:onvo-bg-gray-950/50",
    // disabled
    "disabled:onvo-pointer-events-none",
    "disabled:onvo-bg-gray-100 disabled:onvo-text-gray-400",
    "disabled:dark:onvo-border-gray-800 disabled:dark:onvo-bg-gray-800 disabled:dark:onvo-text-gray-500",
    // focus
    focusInput,
    // invalid (optional)
    // "aria-[invalid=true]:dark:ring-red-400/20 aria-[invalid=true]:ring-2 aria-[invalid=true]:ring-red-200 aria-[invalid=true]:border-red-500 invalid:ring-2 invalid:ring-red-200 invalid:border-red-500"
  ],
  variants: {
    hasError: {
      true: hasErrorInput,
    },
  },
});

interface TriggerProps
  extends React.ComponentProps<"button">,
    VariantProps<typeof triggerStyles> {
  placeholder?: string;
}

const Trigger = React.forwardRef<HTMLButtonElement, TriggerProps>(
  (
    { className, children, placeholder, hasError, ...props }: TriggerProps,
    forwardedRef,
  ) => {
    return (
      <PopoverPrimitives.Trigger asChild>
        <button
          ref={forwardedRef}
          className={cx(triggerStyles({ hasError }), className)}
          {...props}
        >
          <RiCalendar2Fill className="onvo-size-4 onvo-shrink-0 onvo-text-gray-400 dark:onvo-text-gray-600" />
          <span className="onvo-flex-1 onvo-overflow-hidden onvo-text-ellipsis onvo-whitespace-nowrap onvo-text-left onvo-text-gray-900 dark:onvo-text-gray-50">
            {children ? (
              children
            ) : placeholder ? (
              <span className="onvo-text-gray-400 dark:onvo-text-gray-600">
                {placeholder}
              </span>
            ) : null}
          </span>
        </button>
      </PopoverPrimitives.Trigger>
    );
  },
);

Trigger.displayName = "DatePicker.Trigger";

//#region Popover
// ============================================================================

const CalendarPopover = React.forwardRef<
  React.ElementRef<typeof PopoverPrimitives.Content>,
  React.ComponentProps<typeof PopoverPrimitives.Content>
>(({ align, className, children, ...props }, forwardedRef) => {
  return (
    <PopoverPrimitives.Portal>
      <PopoverPrimitives.Content
        ref={forwardedRef}
        sideOffset={10}
        side="bottom"
        align={align}
        avoidCollisions
        onOpenAutoFocus={(e) => e.preventDefault()}
        className={cx(
          // base
          "onvo-relative onvo-z-50 onvo-w-fit onvo-rounded-md onvo-border onvo-text-sm onvo-shadow-xl onvo-shadow-black/[2.5%]",
          // widths
          "onvo-min-w-[calc(var(--radix-select-trigger-width)-2px)] onvo-max-w-[95vw]",
          // border color
          "onvo-border-gray-200 dark:onvo-border-gray-800",
          // background color
          "onvo-bg-white dark:onvo-bg-gray-950",
          // transition
          "onvo-will-change-[transform,opacity]",
          "data-[state=closed]:onvo-animate-hide",
          "data-[state=open]:data-[side=bottom]:onvo-animate-slideDownAndFade data-[state=open]:data-[side=left]:onvo-animate-slideLeftAndFade data-[state=open]:data-[side=right]:onvo-animate-slideRightAndFade data-[state=open]:data-[side=top]:onvo-animate-slideUpAndFade",
          className,
        )}
        {...props}
      >
        {children}
      </PopoverPrimitives.Content>
    </PopoverPrimitives.Portal>
  );
});

CalendarPopover.displayName = "DatePicker.CalendarPopover";

//#region Preset
// ============================================================================

type DateRange = {
  from: Date | undefined;
  to?: Date | undefined;
};

interface Preset {
  label: string;
}

interface DatePreset extends Preset {
  date: Date;
}

interface DateRangePreset extends Preset {
  dateRange: DateRange;
}

type PresetContainerProps<TPreset extends Preset, TValue> = {
  presets: TPreset[];
  onSelect: (value: TValue) => void;
  currentValue?: TValue;
};

const PresetContainer = <TPreset extends Preset, TValue>({
  // Available preset configurations
  presets,
  // Event handler when a preset is selected
  onSelect,
  // Currently selected preset
  currentValue,
}: PresetContainerProps<TPreset, TValue>) => {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const isDateRangePresets = (preset: any): preset is DateRangePreset => {
    return "dateRange" in preset;
  };
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const isDatePresets = (preset: any): preset is DatePreset => {
    return "date" in preset;
  };

  const handleClick = (preset: TPreset) => {
    if (isDateRangePresets(preset)) {
      onSelect(preset.dateRange as TValue);
    } else if (isDatePresets(preset)) {
      onSelect(preset.date as TValue);
    }
  };

  const compareDates = (date1: Date, date2: Date) => {
    return (
      date1.getDate() === date2.getDate() &&
      date1.getMonth() === date2.getMonth() &&
      date1.getFullYear() === date2.getFullYear()
    );
  };

  const compareRanges = (range1: DateRange, range2: DateRange) => {
    const from1 = range1.from;
    const from2 = range2.from;

    let equalFrom = false;

    if (from1 && from2) {
      const sameFrom = compareDates(from1, from2);

      if (sameFrom) {
        equalFrom = true;
      }
    }

    const to1 = range1.to;
    const to2 = range2.to;

    let equalTo = false;

    if (to1 && to2) {
      const sameTo = compareDates(to1, to2);

      if (sameTo) {
        equalTo = true;
      }
    }

    return equalFrom && equalTo;
  };

  const matchesCurrent = (preset: TPreset) => {
    if (isDateRangePresets(preset)) {
      const value = currentValue as DateRange | undefined;

      return value && compareRanges(value, preset.dateRange);
    } else if (isDatePresets(preset)) {
      const value = currentValue as Date | undefined;

      return value && compareDates(value, preset.date);
    }

    return false;
  };

  return (
    <ul className="onvo-flex onvo-items-start onvo-gap-x-2 sm:onvo-flex-col">
      {presets.map((preset, index) => {
        return (
          <li key={index} className="sm:onvo-w-full sm:onvo-py-px">
            <button
              title={preset.label}
              className={cx(
                // base
                "onvo-relative onvo-w-full onvo-overflow-hidden onvo-text-ellipsis onvo-whitespace-nowrap onvo-rounded onvo-border onvo-px-2.5 onvo-py-1.5 onvo-text-left onvo-text-base onvo-shadow-sm onvo-outline-none onvo-transition-all sm:onvo-border-none sm:onvo-py-2 sm:onvo-text-sm sm:onvo-shadow-none",
                // text color
                "onvo-text-gray-700 dark:onvo-text-gray-300",
                // border color
                "onvo-border-gray-200 dark:onvo-border-gray-800",
                // focus
                focusRing,
                // background color
                "focus-visible:onvo-bg-gray-100 focus-visible:dark:onvo-bg-gray-900",
                "hover:onvo-bg-gray-100 hover:dark:onvo-bg-gray-900",
                {
                  "onvo-bg-gray-100 dark:onvo-bg-gray-900":
                    matchesCurrent(preset),
                },
              )}
              onClick={() => handleClick(preset)}
              aria-label={`Select ${preset.label}`}
            >
              <span>{preset.label}</span>
            </button>
          </li>
        );
      })}
    </ul>
  );
};

PresetContainer.displayName = "DatePicker.PresetContainer";

//#region Date Picker Shared
// ============================================================================

const formatDate = (
  date: Date,
  locale: Locale,
  includeTime?: boolean,
): string => {
  const usesAmPm = !isBrowserLocaleClockType24h();
  let dateString: string;

  if (includeTime) {
    dateString = usesAmPm
      ? format(date, "dd MMM, yyyy h:mm a", { locale })
      : format(date, "dd MMM, yyyy HH:mm", { locale });
  } else {
    dateString = format(date, "dd MMM, yyyy", { locale });
  }

  return dateString;
};

type CalendarProps = {
  fromYear?: number;
  toYear?: number;
  fromMonth?: Date;
  toMonth?: Date;
  fromDay?: Date;
  toDay?: Date;
  fromDate?: Date;
  toDate?: Date;
  locale?: Locale;
};

type Translations = {
  cancel?: string;
  apply?: string;
  start?: string;
  end?: string;
  range?: string;
};

interface PickerProps extends CalendarProps {
  className?: string;
  disabled?: boolean;
  disabledDays?: Matcher | Matcher[] | undefined;
  required?: boolean;
  showTimePicker?: boolean;
  placeholder?: string;
  enableYearNavigation?: boolean;
  disableNavigation?: boolean;
  hasError?: boolean;
  id?: string;
  // Customize the date picker for different languages.
  translations?: Translations;
  align?: "center" | "end" | "start";
  "aria-invalid"?: boolean;
  "aria-label"?: string;
  "aria-labelledby"?: string;
  "aria-required"?: boolean;
}

//#region Single Date Picker
// ============================================================================

interface SingleProps extends Omit<PickerProps, "translations"> {
  presets?: DatePreset[];
  defaultValue?: Date;
  value?: Date;
  onChange?: (date: Date | undefined) => void;
  translations?: Omit<Translations, "range">;
}

const SingleDatePicker = ({
  defaultValue,
  value,
  onChange,
  presets,
  disabled,
  disabledDays,
  disableNavigation,
  className,
  showTimePicker,
  placeholder = "Select date",
  hasError,
  translations,
  enableYearNavigation = false,
  locale = enUS,
  align = "center",
  ...props
}: SingleProps) => {
  const [open, setOpen] = React.useState(false);
  const [date, setDate] = React.useState<Date | undefined>(
    value ?? defaultValue ?? undefined,
  );
  const [month, setMonth] = React.useState<Date | undefined>(date);

  const [time, setTime] = React.useState<TimeValue>(
    value
      ? new Time(value.getHours(), value.getMinutes())
      : defaultValue
        ? new Time(defaultValue.getHours(), defaultValue.getMinutes())
        : new Time(0, 0),
  );

  const initialDate = React.useMemo(() => {
    return date;
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open]);

  React.useEffect(() => {
    setDate(value ?? defaultValue ?? undefined);
  }, [value, defaultValue]);

  React.useEffect(() => {
    if (date) {
      setMonth(date);
    }
  }, [date]);

  React.useEffect(() => {
    if (!open) {
      setMonth(date);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open]);

  const onCancel = () => {
    setDate(initialDate);
    setTime(
      initialDate
        ? new Time(initialDate.getHours(), initialDate.getMinutes())
        : new Time(0, 0),
    );
    setOpen(false);
  };

  const onOpenChange = (open: boolean) => {
    if (!open) {
      onCancel();
    }

    setOpen(open);
  };

  const onDateChange = (date: Date | undefined) => {
    const newDate = date;
    if (showTimePicker) {
      if (newDate && !time) {
        setTime(new Time(0, 0));
      }
      if (newDate && time) {
        newDate.setHours(time.hour);
        newDate.setMinutes(time.minute);
      }
    }
    setDate(newDate);
  };

  const onTimeChange = (time: TimeValue | null) => {
    if (!time) return;
    setTime(time);

    if (!date) {
      return;
    }

    const newDate = new Date(date.getTime());

    if (!time) {
      newDate.setHours(0);
      newDate.setMinutes(0);
    } else {
      newDate.setHours(time.hour);
      newDate.setMinutes(time.minute);
    }

    setDate(newDate);
  };

  const formattedDate = React.useMemo(() => {
    if (!date) {
      return null;
    }

    return formatDate(date, locale, showTimePicker);
  }, [date, locale, showTimePicker]);

  const onApply = () => {
    setOpen(false);
    onChange?.(date);
  };

  React.useEffect(() => {
    setDate(value ?? defaultValue ?? undefined);
    setTime(
      value
        ? new Time(value.getHours(), value.getMinutes())
        : defaultValue
          ? new Time(defaultValue.getHours(), defaultValue.getMinutes())
          : new Time(0, 0),
    );
  }, [value, defaultValue]);

  return (
    <PopoverPrimitives.Root
      tremor-id="tremor-raw"
      open={open}
      onOpenChange={onOpenChange}
    >
      <Trigger
        placeholder={placeholder}
        disabled={disabled}
        className={className}
        hasError={hasError}
        aria-required={props.required || props["aria-required"]}
        aria-invalid={props["aria-invalid"]}
        aria-label={props["aria-label"]}
        aria-labelledby={props["aria-labelledby"]}
      >
        {formattedDate}
      </Trigger>
      <CalendarPopover align={align}>
        <div className="onvo-flex onvo-flex-col sm:onvo-flex-row sm:onvo-items-start">
          {presets && presets.length > 0 && (
            <div
              className={cx(
                "onvo-relative onvo-flex onvo-h-14 onvo-w-full onvo-items-center sm:onvo-h-auto sm:onvo-w-40",
                "onvo-border-b onvo-border-gray-200 sm:onvo-border-b-0 sm:onvo-border-r dark:onvo-border-gray-800",
                "onvo-overflow-auto",
              )}
            >
              <div className="onvo-px-2 onvo-pr-2 sm:onvo-inset-0 sm:onvo-left-0 sm:onvo-py-2">
                <PresetContainer
                  currentValue={date}
                  presets={presets}
                  onSelect={onDateChange}
                />
              </div>
            </div>
          )}
          <div>
            <CalendarPrimitive
              mode="single"
              month={month}
              onMonthChange={setMonth}
              selected={date}
              onSelect={onDateChange}
              disabled={disabledDays}
              locale={locale}
              enableYearNavigation={enableYearNavigation}
              disableNavigation={disableNavigation}
              initialFocus
              {...props}
            />
            {showTimePicker && (
              <div className="onvo-border-t onvo-border-gray-200 onvo-p-3 dark:onvo-border-gray-800">
                <TimeInput
                  aria-label="Time"
                  onChange={onTimeChange}
                  isDisabled={!date}
                  value={time}
                  isRequired={props.required}
                />
              </div>
            )}
            <div className="onvo-flex onvo-items-center onvo-gap-x-2 onvo-border-t onvo-border-gray-200 onvo-p-3 dark:onvo-border-gray-800">
              <Button
                variant="secondary"
                className="onvo-h-8 onvo-w-full"
                type="button"
                onClick={onCancel}
              >
                {translations?.cancel ?? "Cancel"}
              </Button>
              <Button
                variant="primary"
                className="onvo-h-8 onvo-w-full"
                type="button"
                onClick={onApply}
              >
                {translations?.apply ?? "Apply"}
              </Button>
            </div>
          </div>
        </div>
      </CalendarPopover>
    </PopoverPrimitives.Root>
  );
};

//#region Range Date Picker
// ============================================================================

interface RangeProps extends PickerProps {
  presets?: DateRangePreset[];
  defaultValue?: DateRange;
  value?: DateRange;
  onChange?: (dateRange: DateRange | undefined) => void;
  maxDays?: number;
}

const RangeDatePicker = ({
  defaultValue,
  value,
  onChange,
  presets,
  disabled,
  disableNavigation,
  disabledDays,
  enableYearNavigation = false,
  locale = enUS,
  showTimePicker,
  placeholder = "Select date range",
  hasError,
  translations,
  align = "center",
  className,
  maxDays,
  ...props
}: RangeProps) => {
  const [open, setOpen] = React.useState(false);
  const [range, setRange] = React.useState<DateRange | undefined>(
    value ?? defaultValue ?? undefined,
  );
  const [month, setMonth] = React.useState<Date | undefined>(range?.from);

  const [startTime, setStartTime] = React.useState<TimeValue>(
    value?.from
      ? new Time(value.from.getHours(), value.from.getMinutes())
      : defaultValue?.from
        ? new Time(defaultValue.from.getHours(), defaultValue.from.getMinutes())
        : new Time(0, 0),
  );
  const [endTime, setEndTime] = React.useState<TimeValue>(
    value?.to
      ? new Time(value.to.getHours(), value.to.getMinutes())
      : defaultValue?.to
        ? new Time(defaultValue.to.getHours(), defaultValue.to.getMinutes())
        : new Time(0, 0),
  );

  const initialRange = React.useMemo(() => {
    return range;
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open]);

  React.useEffect(() => {
    setRange(value ?? defaultValue ?? undefined);
  }, [value, defaultValue]);

  React.useEffect(() => {
    if (range) {
      setMonth(range.from);
    }
  }, [range]);

  React.useEffect(() => {
    if (!open) {
      setMonth(range?.from);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open]);

  const onRangeChange = (range: DateRange | undefined) => {
    const newRange = range;

    if (showTimePicker) {
      if (newRange?.from && !startTime) {
        setStartTime(new Time(0, 0));
      }

      if (newRange?.to && !endTime) {
        setEndTime(new Time(0, 0));
      }

      if (newRange?.from && startTime) {
        newRange.from.setHours(startTime.hour);
        newRange.from.setMinutes(startTime.minute);
      }

      if (newRange?.to && endTime) {
        newRange.to.setHours(endTime.hour);
        newRange.to.setMinutes(endTime.minute);
      }
    }

    setRange(newRange);
  };

  const onCancel = () => {
    setRange(initialRange);
    setStartTime(
      initialRange?.from
        ? new Time(initialRange.from.getHours(), initialRange.from.getMinutes())
        : new Time(0, 0),
    );
    setEndTime(
      initialRange?.to
        ? new Time(initialRange.to.getHours(), initialRange.to.getMinutes())
        : new Time(0, 0),
    );
    setOpen(false);
  };

  const onOpenChange = (open: boolean) => {
    if (!open) {
      onCancel();
    }

    setOpen(open);
  };

  const onTimeChange = (time: TimeValue | null, pos: "start" | "end") => {
    if (!time) return;
    switch (pos) {
      case "start":
        setStartTime(time);
        break;
      case "end":
        setEndTime(time);
        break;
    }

    if (!range) {
      return;
    }

    if (pos === "start") {
      if (!range.from) {
        return;
      }

      const newDate = new Date(range.from.getTime());

      if (!time) {
        newDate.setHours(0);
        newDate.setMinutes(0);
      } else {
        newDate.setHours(time.hour);
        newDate.setMinutes(time.minute);
      }

      setRange({
        ...range,
        from: newDate,
      });
    }

    if (pos === "end") {
      if (!range.to) {
        return;
      }

      const newDate = new Date(range.to.getTime());

      if (!time) {
        newDate.setHours(0);
        newDate.setMinutes(0);
      } else {
        newDate.setHours(time.hour);
        newDate.setMinutes(time.minute);
      }

      setRange({
        ...range,
        to: newDate,
      });
    }
  };

  React.useEffect(() => {
    setRange(value ?? defaultValue ?? undefined);

    setStartTime(
      value?.from
        ? new Time(value.from.getHours(), value.from.getMinutes())
        : defaultValue?.from
          ? new Time(
              defaultValue.from.getHours(),
              defaultValue.from.getMinutes(),
            )
          : new Time(0, 0),
    );
    setEndTime(
      value?.to
        ? new Time(value.to.getHours(), value.to.getMinutes())
        : defaultValue?.to
          ? new Time(defaultValue.to.getHours(), defaultValue.to.getMinutes())
          : new Time(0, 0),
    );
  }, [value, defaultValue]);

  const displayRange = React.useMemo(() => {
    if (!range) {
      return null;
    }

    return `${range.from ? formatDate(range.from, locale, showTimePicker) : ""} - ${
      range.to ? formatDate(range.to, locale, showTimePicker) : ""
    }`;
  }, [range, locale, showTimePicker]);

  const onApply = () => {
    setOpen(false);
    onChange?.(range);
  };

  return (
    <PopoverPrimitives.Root
      tremor-id="tremor-raw"
      open={open}
      onOpenChange={onOpenChange}
    >
      <Trigger
        placeholder={placeholder}
        disabled={disabled}
        className={className}
        hasError={hasError}
        aria-required={props.required || props["aria-required"]}
        aria-invalid={props["aria-invalid"]}
        aria-label={props["aria-label"]}
        aria-labelledby={props["aria-labelledby"]}
      >
        {displayRange}
      </Trigger>
      <CalendarPopover align={align}>
        <div className="onvo-flex">
          <div className="onvo-flex onvo-flex-col onvo-overflow-x-auto sm:onvo-flex-row sm:onvo-items-stretch">
            {presets && presets.length > 0 && (
              <div
                className={cx(
                  "onvo-relative onvo-flex onvo-h-16 onvo-w-full onvo-items-center sm:onvo-h-auto sm:onvo-w-40",
                  "onvo-border-b onvo-border-gray-200 sm:onvo-border-b-0 sm:onvo-border-r dark:onvo-border-gray-800",
                  "onvo-overflow-x-auto sm:onvo-overflow-y-auto",
                )}
              >
                <div className="onvo-absolute onvo-px-3 sm:onvo-inset-0 sm:onvo-left-0 sm:onvo-p-2">
                  <PresetContainer
                    currentValue={range}
                    presets={presets}
                    onSelect={onRangeChange}
                  />
                </div>
              </div>
            )}
            <div className="onvo-overflow-x-auto">
              <CalendarPrimitive
                mode="range"
                selected={range}
                onSelect={onRangeChange}
                month={month}
                onMonthChange={setMonth}
                numberOfMonths={2}
                disabled={disabledDays}
                disableNavigation={disableNavigation}
                enableYearNavigation={enableYearNavigation}
                max={maxDays}
                locale={locale}
                initialFocus
                classNames={{
                  months:
                    "onvo-flex onvo-flex-row onvo-divide-x onvo-divide-gray-200 dark:onvo-divide-gray-800 onvo-overflow-x-auto",
                }}
                {...props}
              />
              {showTimePicker && (
                <div className="onvo-flex onvo-items-center onvo-justify-evenly onvo-gap-x-3 onvo-border-t onvo-border-gray-200 onvo-p-3 dark:onvo-border-gray-800">
                  <div className="onvo-flex onvo-flex-1 onvo-items-center onvo-gap-x-2">
                    <span className="dark:onvo-text-gray-30 onvo-text-gray-700">
                      {translations?.start ?? "Start"}:
                    </span>
                    <TimeInput
                      value={startTime}
                      onChange={(v) => onTimeChange(v, "start")}
                      aria-label="Start date time"
                      isDisabled={!range?.from}
                      isRequired={props.required}
                    />
                  </div>
                  <RiSubtractFill className="onvo-size-4 onvo-shrink-0 onvo-text-gray-400" />
                  <div className="onvo-flex onvo-flex-1 onvo-items-center onvo-gap-x-2">
                    <span className="dark:onvo-text-gray-30 onvo-text-gray-700">
                      {translations?.end ?? "End"}:
                    </span>
                    <TimeInput
                      value={endTime}
                      onChange={(v) => onTimeChange(v, "end")}
                      aria-label="End date time"
                      isDisabled={!range?.to}
                      isRequired={props.required}
                    />
                  </div>
                </div>
              )}
              <div className="onvo-border-t onvo-border-gray-200 onvo-p-3 sm:onvo-flex sm:onvo-items-center sm:onvo-justify-between dark:onvo-border-gray-800">
                <p className="onvo-tabular-nums onvo-text-gray-900 dark:onvo-text-gray-50">
                  <span className="onvo-text-gray-700 dark:onvo-text-gray-300">
                    {translations?.range ?? "Range"}:
                  </span>{" "}
                  <span className="onvo-font-medium">{displayRange}</span>{" "}
                  {range?.to && range?.from && (
                    <p
                      onClick={() => setRange(undefined)}
                      className="onvo-underline onvo-cursor-pointer"
                    >
                      Reset
                    </p>
                  )}
                </p>
                <div className="onvo-mt-2 onvo-flex onvo-items-center onvo-gap-x-2 sm:onvo-mt-0">
                  <Button
                    variant="secondary"
                    className="onvo-h-8 onvo-w-full sm:onvo-w-fit"
                    type="button"
                    onClick={onCancel}
                  >
                    {translations?.cancel ?? "Cancel"}
                  </Button>
                  <Button
                    variant="primary"
                    className="onvo-h-8 onvo-w-full sm:onvo-w-fit"
                    type="button"
                    onClick={onApply}
                  >
                    {translations?.apply ?? "Apply"}
                  </Button>
                </div>
              </div>
            </div>
          </div>
        </div>
      </CalendarPopover>
    </PopoverPrimitives.Root>
  );
};

//#region Preset Validation
// ============================================================================

const validatePresets = (
  presets: DateRangePreset[] | DatePreset[],
  rules: PickerProps,
) => {
  const { toYear, fromYear, fromMonth, toMonth, fromDay, toDay } = rules;

  if (presets && presets.length > 0) {
    const fromYearToUse = fromYear;
    const toYearToUse = toYear;

    presets.forEach((preset) => {
      if ("date" in preset) {
        const presetYear = preset.date.getFullYear();

        if (fromYear && presetYear < fromYear) {
          throw new Error(
            `Preset ${preset.label} is before fromYear ${fromYearToUse}.`,
          );
        }

        if (toYear && presetYear > toYear) {
          throw new Error(
            `Preset ${preset.label} is after toYear ${toYearToUse}.`,
          );
        }

        if (fromMonth) {
          const presetMonth = preset.date.getMonth();

          if (presetMonth < fromMonth.getMonth()) {
            throw new Error(
              `Preset ${preset.label} is before fromMonth ${fromMonth}.`,
            );
          }
        }

        if (toMonth) {
          const presetMonth = preset.date.getMonth();

          if (presetMonth > toMonth.getMonth()) {
            throw new Error(
              `Preset ${preset.label} is after toMonth ${toMonth}.`,
            );
          }
        }

        if (fromDay) {
          const presetDay = preset.date.getDate();

          if (presetDay < fromDay.getDate()) {
            throw new Error(
              `Preset ${preset.label} is before fromDay ${fromDay}.`,
            );
          }
        }

        if (toDay) {
          const presetDay = preset.date.getDate();

          if (presetDay > toDay.getDate()) {
            throw new Error(
              `Preset ${preset.label} is after toDay ${format(
                toDay,
                "MMM dd, yyyy",
              )}.`,
            );
          }
        }
      }

      if ("dateRange" in preset) {
        const presetFromYear = preset.dateRange.from?.getFullYear();
        const presetToYear = preset.dateRange.to?.getFullYear();

        if (presetFromYear && fromYear && presetFromYear < fromYear) {
          throw new Error(
            `Preset ${preset.label}'s 'from' is before fromYear ${fromYearToUse}.`,
          );
        }

        if (presetToYear && toYear && presetToYear > toYear) {
          throw new Error(
            `Preset ${preset.label}'s 'to' is after toYear ${toYearToUse}.`,
          );
        }

        if (fromMonth) {
          const presetMonth = preset.dateRange.from?.getMonth();

          if (presetMonth && presetMonth < fromMonth.getMonth()) {
            throw new Error(
              `Preset ${preset.label}'s 'from' is before fromMonth ${format(
                fromMonth,
                "MMM, yyyy",
              )}.`,
            );
          }
        }

        if (toMonth) {
          const presetMonth = preset.dateRange.to?.getMonth();

          if (presetMonth && presetMonth > toMonth.getMonth()) {
            throw new Error(
              `Preset ${preset.label}'s 'to' is after toMonth ${format(
                toMonth,
                "MMM, yyyy",
              )}.`,
            );
          }
        }

        if (fromDay) {
          const presetDay = preset.dateRange.from?.getDate();

          if (presetDay && presetDay < fromDay.getDate()) {
            throw new Error(
              `Preset ${
                preset.dateRange.from
              }'s 'from' is before fromDay ${format(fromDay, "MMM dd, yyyy")}.`,
            );
          }
        }

        if (toDay) {
          const presetDay = preset.dateRange.to?.getDate();

          if (presetDay && presetDay > toDay.getDate()) {
            throw new Error(
              `Preset ${preset.label}'s 'to' is after toDay ${format(
                toDay,
                "MMM dd, yyyy",
              )}.`,
            );
          }
        }
      }
    });
  }
};

//#region Types & Exports
// ============================================================================

type SingleDatePickerProps = {
  presets?: DatePreset[];
  defaultValue?: Date;
  value?: Date;
  onChange?: (date: Date | undefined) => void;
} & PickerProps;

const DatePicker = ({ presets, ...props }: SingleDatePickerProps) => {
  if (presets) {
    validatePresets(presets, props);
  }

  return <SingleDatePicker presets={presets} {...(props as SingleProps)} />;
};

DatePicker.displayName = "DatePicker";

type RangeDatePickerProps = {
  presets?: DateRangePreset[];
  defaultValue?: DateRange;
  value?: DateRange;
  onChange?: (dateRange: DateRange | undefined) => void;
  maxDays?: number;
} & PickerProps;

const DateRangePicker = ({ presets, ...props }: RangeDatePickerProps) => {
  if (presets) {
    validatePresets(presets, props);
  }

  return <RangeDatePicker presets={presets} {...(props as RangeProps)} />;
};

DateRangePicker.displayName = "DateRangePicker";

export {
  DatePicker,
  DateRangePicker,
  type DatePreset,
  type DateRangePreset,
  type DateRange,
};
