function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
/*
 * Copyright 2025 Palantir Technologies, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { Input } from "@base-ui/react/input";
import { Popover } from "@base-ui/react/popover";
import classnames from "classnames";
import React, { useCallback, useId, useRef, useState } from "react";
import { formatDateForInput, formatDatetimeForInput, parseDateFromInput, parseDatetimeFromInput } from "../dateUtils.js";
import { PortalDismissLayer } from "../PortalDismissLayer.js";
import { stopPropagation } from "./calendarShared.js";
import { LazyDateRangeCalendar } from "./LazyDateRangeCalendar.js";
import { TimePicker } from "./TimePicker.js";
import { useDateEditState } from "./useDateEditState.js";
import commonStyles from "./DatePickerCommon.module.css.js";
import styles from "./DateRangePicker.module.css.js";

/**
 * A date range represented as a start/end tuple. Either element may be
 * `null` when the range is partially selected.
 */

/** Default empty range — both bounds are null. */
export const EMPTY_RANGE = [null, null];

/**
 * Props for the shared DateRangePicker. Used by filter-list's date-range
 * histogram and action-form's `DATE_RANGE_INPUT` field kind. `id` and
 * `error` are optional so non-form callers can omit them.
 *
 * Renders two text inputs (start / end) with a shared calendar popover
 * supporting range selection.
 */

// Shared props for both start/end inputs. role="combobox" because each input
// triggers a shared popup (the calendar popover) — matching WAI-ARIA combobox pattern.
const SHARED_INPUT_PROPS = {
  className: commonStyles.osdkDatePickerInput,
  type: "text",
  onClick: stopPropagation,
  autoComplete: "off",
  role: "combobox",
  "aria-haspopup": "dialog"
};
export const DateRangePicker = /*#__PURE__*/React.memo(function ({
  id,
  value,
  onChange,
  min,
  max,
  placeholderStart,
  placeholderEnd,
  allowSingleDayRange = true,
  showTime = false,
  formatDate,
  parseDate,
  portalContainer,
  modal = "trap-focus",
  disabled = false
}) {
  const shouldCloseOnSelection = !showTime;
  const popoverId = useId();
  // The range container anchors the shared popover without becoming a trigger.
  // Each input is its own Popover.Trigger so the comboboxes are not nested in an
  // interactive wrapper.
  const triggerRef = useRef(null);
  const startInputRef = useRef(null);
  const endInputRef = useRef(null);
  const popoverRef = useRef(null);
  const [isOpen, setIsOpen] = useState(false);
  // When focus returns to an input after Tab exits the popover boundary, the
  // next input focus should not reopen the calendar before native Tab can
  // continue to the following form field.
  const skipReopenRef = useRef(false);
  // Tracks which input (start/end) owns the shared calendar popover.
  // Used to restore focus to the correct input when Tab-cycling through
  // focus boundaries and when the calendar selects a range endpoint.
  const [activeBoundary, setActiveBoundary] = useState("start");
  const [startDate, endDate] = value ?? EMPTY_RANGE;

  // editFormatFn produces a parsable string for typing (e.g. "2024-01-15" or "2024-01-15 14:30").
  // displayFormatFn produces the idle string. Defaults stay deterministic so
  // users in different browser locales see the same date in form inputs.
  const editFormatFn = showTime ? formatDatetimeForInput : formatDateForInput;
  const displayFormatFn = formatDate ?? (showTime ? formatDatetimeForInput : formatDateForInput);
  const parseFn = parseDate ?? (showTime ? parseDatetimeFromInput : parseDateFromInput);

  // Wrap onChange to handle tuple construction and overlap rejection.
  // Clearing (null) is always allowed; overlap is checked for non-null dates.
  const startOnChange = useCallback(date => {
    if (date != null && isOverlapping(date, endDate, allowSingleDayRange)) {
      return;
    }
    onChange?.([date, endDate ?? null]);
  }, [endDate, onChange, allowSingleDayRange]);
  const endOnChange = useCallback(date => {
    if (date != null && isOverlapping(startDate, date, allowSingleDayRange)) {
      return;
    }
    onChange?.([startDate ?? null, date]);
  }, [startDate, onChange, allowSingleDayRange]);
  const {
    isEditing: isEditingStart,
    dateValue: startParsedValue,
    inputError: startInputError,
    displayedValue: displayedStart,
    startEditing: beginStartEditing,
    stopEditing: stopStartEditing,
    commitAndStopEditing: commitStartAndStopEditing,
    setInputValue: setStartInputValue,
    setDateValue: setStartDateValue
  } = useDateEditState({
    value: startDate,
    displayFormatFn,
    editFormatFn,
    parseFn,
    min,
    max,
    onChange: startOnChange
  });
  const {
    isEditing: isEditingEnd,
    dateValue: endParsedValue,
    inputError: endInputError,
    displayedValue: displayedEnd,
    startEditing: beginEndEditing,
    stopEditing: stopEndEditing,
    commitAndStopEditing: commitEndAndStopEditing,
    setInputValue: setEndInputValue,
    setDateValue: setEndDateValue
  } = useDateEditState({
    value: endDate,
    displayFormatFn,
    editFormatFn,
    parseFn,
    min,
    max,
    onChange: endOnChange
  });

  // --- Cross-input error: overlapping range (live feedback while typing) ---
  // Blur/Enter handlers prevent overlapping values from being committed,
  // so this only fires during editing for immediate red-border feedback.
  const hasOverlapError = (() => {
    if (!isEditingStart && !isEditingEnd) return false;
    const effectiveStart = isEditingStart ? startParsedValue : startDate ?? undefined;
    const effectiveEnd = isEditingEnd ? endParsedValue : endDate ?? undefined;
    return isOverlapping(effectiveStart, effectiveEnd, allowSingleDayRange);
  })();
  const startInvalid = startInputError != null || hasOverlapError;
  const endInvalid = endInputError != null || hasOverlapError;
  const activeStartDateValue = startInputError == null && startParsedValue != null ? startParsedValue : startDate ?? undefined;
  const activeEndDateValue = endInputError == null && endParsedValue != null ? endParsedValue : endDate ?? undefined;

  // --- Focus handlers ---

  const getActiveInputRef = useCallback(() => activeBoundary === "start" ? startInputRef : endInputRef, [activeBoundary]);
  const beginEditing = useCallback(boundary => {
    if (boundary === "start") {
      beginStartEditing();
    } else {
      beginEndEditing();
    }
    setActiveBoundary(boundary);
  }, [beginStartEditing, beginEndEditing]);
  const handleInputFocus = useCallback(boundary => {
    beginEditing(boundary);
    if (skipReopenRef.current) {
      skipReopenRef.current = false;
      return;
    }
    setIsOpen(true);
  }, [beginEditing]);
  const handleStartFocus = useCallback(() => {
    handleInputFocus("start");
  }, [handleInputFocus]);
  const handleEndFocus = useCallback(() => {
    handleInputFocus("end");
  }, [handleInputFocus]);
  const closePopoverForBoundaryExit = useCallback(() => {
    skipReopenRef.current = true;
    setIsOpen(false);
    stopStartEditing();
    stopEndEditing();
    getActiveInputRef().current?.focus();
  }, [getActiveInputRef, stopStartEditing, stopEndEditing]);
  const handleStartPointerDown = useCallback(() => {
    // Opening from pointer-down keeps mouse interactions in sync with focus
    // editing before Base UI's later click trigger handler runs.
    startInputRef.current?.focus();
    handleStartFocus();
  }, [handleStartFocus]);
  const handleEndPointerDown = useCallback(() => {
    // Opening from pointer-down keeps mouse interactions in sync with focus
    // editing before Base UI's later click trigger handler runs.
    endInputRef.current?.focus();
    handleEndFocus();
  }, [handleEndFocus]);

  // --- Blur handlers ---

  const handleStartBlur = useCallback(e => {
    const related = e.relatedTarget ?? document.activeElement;
    if (popoverRef.current?.contains(related)) {
      // Focus moved into the popover portal — the field is still logically
      // active, so suppress the blur from bubbling to parent containers.
      e.stopPropagation();
      return;
    }
    if (endInputRef.current === related) {
      return;
    }
    commitStartAndStopEditing();
  }, [commitStartAndStopEditing]);
  const handleEndBlur = useCallback(e => {
    const related = e.relatedTarget ?? document.activeElement;
    if (popoverRef.current?.contains(related)) {
      e.stopPropagation();
      return;
    }
    if (startInputRef.current === related) {
      return;
    }
    commitEndAndStopEditing();
  }, [commitEndAndStopEditing]);

  // --- Popover helpers ---

  // Shared close sequence: dismiss the popover, reset both editing states,
  // and blur both inputs so focus doesn't linger after the calendar disappears.
  const closePopover = useCallback(() => {
    setIsOpen(false);
    stopStartEditing();
    stopEndEditing();
    startInputRef.current?.blur();
    endInputRef.current?.blur();
  }, [stopStartEditing, stopEndEditing]);

  // --- Keyboard handlers ---

  const handleStartKeyDown = useCallback(e => {
    if (e.key === "Enter") {
      e.preventDefault();
      commitStartAndStopEditing();
      // Auto-advance to end
      endInputRef.current?.focus();
    } else if (e.key === "Escape") {
      e.preventDefault();
      closePopover();
    } else if (e.key === "Tab" && e.shiftKey) {
      setIsOpen(false);
    }
  }, [commitStartAndStopEditing, closePopover]);
  const handleEndKeyDown = useCallback(e => {
    if (e.key === "Enter") {
      e.preventDefault();
      commitEndAndStopEditing();
      closePopover();
    } else if (e.key === "Escape") {
      e.preventDefault();
      closePopover();
    } else if (e.key === "Tab" && !e.shiftKey && isOpen) {
      // Tab from the end input bridges focus into the popover. The popover
      // doesn't auto-focus on open (to keep the cursor in the input for typing),
      // so we manually focus the first interactive element (nav button or select).
      const firstButton = popoverRef.current?.querySelector("button, select");
      if (firstButton != null) {
        e.preventDefault();
        firstButton.focus();
      }
    }
  }, [commitEndAndStopEditing, closePopover, isOpen]);

  // Called by base-ui when the popover opens or closes (e.g. click outside, Escape).
  const handleOpenChange = useCallback(nextOpen => {
    if (nextOpen) {
      setIsOpen(true);
    } else {
      closePopover();
    }
  }, [closePopover]);

  // --- Calendar handlers ---

  const handleRangeSelect = useCallback(range => {
    const newStart = range?.from ?? null;
    const newEnd = range?.to ?? null;
    onChange?.([newStart, newEnd]);
    if (newStart != null && newEnd == null) {
      // Start selected — commit the start and advance to end.
      // displayedValue handles the display format after stopEditing.
      stopStartEditing();
      setActiveBoundary("end");
      endInputRef.current?.focus();
    } else if (newStart != null && newEnd != null && shouldCloseOnSelection) {
      // Full range selected — close and blur.
      closePopover();
    } else if (newStart != null && newEnd != null) {
      // Full range selected but popover stays open (showTime) —
      // inputs remain in editing mode, so update with editFormatFn.
      setStartDateValue(newStart);
      setEndDateValue(newEnd);
    }
  }, [onChange, shouldCloseOnSelection, closePopover, stopStartEditing, setStartDateValue, setEndDateValue]);

  // --- Time handlers ---

  const handleStartTimeChange = useCallback(time => {
    onChange?.([time, endDate ?? null]);
    setStartDateValue(time);
  }, [endDate, onChange, setStartDateValue]);
  const handleEndTimeChange = useCallback(time => {
    onChange?.([startDate ?? null, time]);
    setEndDateValue(time);
  }, [startDate, onChange, setEndDateValue]);

  // --- Focus boundary handlers ---
  // Visually-hidden sentinels at the top/bottom of the popover that trap Tab
  // cycling between the text inputs and calendar.

  // Start boundary (top): Shift+Tab past the first calendar element redirects
  // focus to whichever input is currently active.
  const handleStartFocusBoundary = useCallback(() => {
    getActiveInputRef().current?.focus();
  }, [getActiveInputRef]);

  // End boundary (bottom): Two cases —
  // (1) Tab past the last calendar element (focus came from inside the popover)
  //     → close the popover and return focus to the active input so the next
  //       native Tab continues to the next form field.
  // (2) Focus entered from outside the popover (e.g. reverse Tab from the page)
  //     → redirect to the last interactive element inside the popover.
  const handleEndFocusBoundary = useCallback(e => {
    const related = e.relatedTarget ?? document.activeElement;
    if (popoverRef.current?.contains(related)) {
      closePopoverForBoundaryExit();
    } else {
      const buttons = popoverRef.current?.querySelectorAll("button, select");
      const lastButton = buttons?.[buttons.length - 1];
      lastButton?.focus();
    }
  }, [closePopoverForBoundaryExit]);

  // --- Calendar selected range ---

  const calendarSelected = startDate != null || endDate != null ? {
    from: startDate ?? undefined,
    to: endDate ?? undefined
  } : undefined;
  const timeFooter = showTime ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(TimePicker, {
    value: activeStartDateValue ?? null,
    onChange: handleStartTimeChange,
    label: "Start time"
  }), /*#__PURE__*/React.createElement(TimePicker, {
    value: activeEndDateValue ?? null,
    onChange: handleEndTimeChange,
    label: "End time"
  })) : undefined;
  const sharedInputProps = {
    ...SHARED_INPUT_PROPS,
    "aria-controls": popoverId
  };
  const isPopoverOpen = !disabled && isOpen;

  // Keep Popover.Trigger on each input itself. Moving it to the range wrapper
  // would make click handling simpler, but it would also nest interactive
  // comboboxes inside an interactive trigger and reintroduce the axe violation.
  return /*#__PURE__*/React.createElement(Popover.Root, {
    open: isPopoverOpen,
    onOpenChange: handleOpenChange,
    modal: modal
  }, /*#__PURE__*/React.createElement("div", {
    ref: triggerRef,
    className: styles.osdkDateRangeContainer,
    "data-disabled": disabled || undefined
  }, /*#__PURE__*/React.createElement("div", {
    className: classnames(commonStyles.osdkDatePickerInputWrapper, styles.osdkDateRangeInputWrapper, startInvalid && commonStyles.osdkDatePickerInputWrapperError),
    "data-disabled": disabled || undefined
  }, /*#__PURE__*/React.createElement(Popover.Trigger, {
    nativeButton: false,
    render: /*#__PURE__*/React.createElement(Input, _extends({
      ref: startInputRef,
      id: id != null ? `${id}-start` : undefined,
      value: displayedStart,
      onValueChange: setStartInputValue,
      disabled: disabled,
      onFocus: handleStartFocus,
      onPointerDown: handleStartPointerDown,
      onBlur: handleStartBlur,
      onKeyDown: handleStartKeyDown,
      placeholder: placeholderStart,
      "aria-expanded": isPopoverOpen && activeBoundary === "start",
      "aria-label": "Start date",
      "aria-invalid": startInvalid || undefined
    }, sharedInputProps))
  })), /*#__PURE__*/React.createElement("div", {
    className: classnames(commonStyles.osdkDatePickerInputWrapper, styles.osdkDateRangeInputWrapper, endInvalid && commonStyles.osdkDatePickerInputWrapperError),
    "data-disabled": disabled || undefined
  }, /*#__PURE__*/React.createElement(Popover.Trigger, {
    nativeButton: false,
    render: /*#__PURE__*/React.createElement(Input, _extends({
      ref: endInputRef,
      id: id != null ? `${id}-end` : undefined,
      value: displayedEnd,
      onValueChange: setEndInputValue,
      disabled: disabled,
      onBlur: handleEndBlur,
      onKeyDown: handleEndKeyDown,
      onFocus: handleEndFocus,
      onPointerDown: handleEndPointerDown,
      placeholder: placeholderEnd,
      "aria-expanded": isPopoverOpen && activeBoundary === "end",
      "aria-label": "End date",
      "aria-invalid": endInvalid || undefined
    }, sharedInputProps))
  }))), /*#__PURE__*/React.createElement(Popover.Portal, {
    container: portalContainer
  }, /*#__PURE__*/React.createElement(PortalDismissLayer, {
    className: commonStyles.osdkDatePickerDismissLayer,
    onDismiss: closePopover
  }), /*#__PURE__*/React.createElement(Popover.Positioner, {
    anchor: triggerRef,
    className: commonStyles.osdkDatePickerPositioner,
    sideOffset: 4,
    side: "bottom",
    align: "start"
  }, /*#__PURE__*/React.createElement(Popover.Popup, {
    ref: popoverRef,
    className: commonStyles.osdkDatePickerPopover,
    id: popoverId,
    role: "dialog",
    "aria-label": "date range picker"
    // Disable base-ui's automatic focus restoration to the trigger on close.
    // We manage focus ourselves via closePopover() which blurs the inputs.
    ,
    finalFocus: false
  }, /*#__PURE__*/React.createElement("div", {
    onFocus: handleStartFocusBoundary,
    tabIndex: 0,
    "aria-label": "Start of date range picker dialog",
    className: commonStyles.osdkDatePickerFocusBoundary
  }), /*#__PURE__*/React.createElement(LazyDateRangeCalendar, {
    selected: calendarSelected,
    onSelect: handleRangeSelect,
    min: min,
    max: max,
    footer: timeFooter
  }), /*#__PURE__*/React.createElement("div", {
    onFocus: handleEndFocusBoundary,
    tabIndex: 0,
    "aria-label": "End of date range picker dialog",
    className: commonStyles.osdkDatePickerFocusBoundary
  })))));
});

/** True when the end boundary is before (or same-day when disallowed) the start. */
function isOverlapping(start, end, allowSingleDayRange) {
  if (start == null || end == null) return false;
  if (!allowSingleDayRange && end.getTime() === start.getTime()) return true;
  return end.getTime() < start.getTime();
}
//# sourceMappingURL=DateRangePicker.js.map