import { useState, useCallback, useLayoutEffect, useEffect, useRef, forwardRef } from 'react';
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import { c as cn } from './utils-BJnb9o5c.js';
import { Button } from './primitives/Button.js';
import { ScrollArea } from './primitives/ScrollArea.js';
import pureDayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import { dayjs } from '@bigbinary/neeto-commons-frontend/utils';

function useControlledOpen(controlledOpen, onOpenChange) {
  const [internalOpen, setInternalOpen] = useState(false);
  const isControlled = controlledOpen !== void 0;
  const open = isControlled ? controlledOpen : internalOpen;
  const setOpen = useCallback(
    (next) => {
      if (!isControlled) setInternalOpen(next);
      onOpenChange?.(next);
    },
    [isControlled, onOpenChange]
  );
  return { open, setOpen };
}

function useCursorRestore(targets) {
  useLayoutEffect(() => {
    for (const { inputRef, cursorRef } of targets) {
      const inputEl = inputRef.current;
      const cursor = cursorRef.current;
      if (cursor !== null && inputEl && document.activeElement === inputEl) {
        inputEl.setSelectionRange(cursor, cursor);
        cursorRef.current = null;
      }
    }
  });
}

function useOutsideClickClose({
  enabled,
  containerRef,
  popoverElementId,
  onClose
}) {
  useEffect(() => {
    if (!enabled) return;
    const handlePointerDown = (e) => {
      const target = e.target;
      if (containerRef.current?.contains(target)) return;
      const popoverEl = document.getElementById(popoverElementId);
      if (popoverEl?.contains(target)) return;
      onClose();
    };
    document.addEventListener("pointerdown", handlePointerDown);
    return () => document.removeEventListener("pointerdown", handlePointerDown);
  }, [enabled, containerRef, popoverElementId, onClose]);
}

const FORMAT_CHARS = /* @__PURE__ */ new Set(["d", "M", "y", "H", "h", "m", "s"]);
const DIGIT = /\d/;
const AMPM_CHAR = /[APM]/;
const VALID_CHAR = /[\dAPM]/i;
const isSlot = (ch) => ch === "#" || ch === "@";
const isValidChar = (ch, hasAmPm) => DIGIT.test(ch) || hasAmPm && AMPM_CHAR.test(ch);
const buildMaskTemplate = (format) => {
  let pattern = "";
  let hasAmPm = false;
  for (const char of format) {
    if (FORMAT_CHARS.has(char)) {
      pattern += "#";
    } else if (char === "A") {
      pattern += "@";
      hasAmPm = true;
    } else {
      pattern += char;
    }
  }
  return { pattern, hasAmPm };
};
const buildRangeMaskTemplate = (format) => {
  const single = buildMaskTemplate(format);
  return {
    pattern: `${single.pattern} - ${single.pattern}`,
    hasAmPm: single.hasAmPm
  };
};
const TEXTUAL_WHEN_LONG = /* @__PURE__ */ new Set(["M", "d"]);
const isFixedWidthFormat = (format) => {
  let i = 0;
  while (i < format.length) {
    const char = format[i];
    if (!FORMAT_CHARS.has(char)) {
      i++;
      continue;
    }
    let count = 0;
    while (i + count < format.length && format[i + count] === char) count++;
    if (count === 1) return false;
    if (count >= 3 && TEXTUAL_WHEN_LONG.has(char)) return false;
    i += count;
  }
  return true;
};
const findDeletionPoint = (prev, input) => {
  for (let i = 0; i < prev.length; i++) {
    if (i >= input.length || input[i] !== prev[i]) return i;
  }
  return prev.length - 1;
};
const hasDigitsAfter = (text, pos, pattern) => {
  for (let i = pos + 1; i < text.length && i < pattern.length; i++) {
    if (isSlot(pattern[i]) && text[i] !== "_") return true;
  }
  return false;
};
const countValidBefore = (text, pos, hasAmPm) => {
  let count = 0;
  for (let i = 0; i < pos && i < text.length; i++) {
    if (isValidChar(text[i].toUpperCase(), hasAmPm)) count++;
  }
  return count;
};
const extractWithPattern = (text, pattern) => {
  const chars = [];
  const upper = text.toUpperCase();
  let pIdx = 0;
  for (let i = 0; i < upper.length; i++) {
    const ch = upper[i];
    while (pIdx < pattern.length && !isSlot(pattern[pIdx])) pIdx++;
    if (pIdx >= pattern.length) break;
    if (pattern[pIdx] === "#" && DIGIT.test(ch)) {
      chars.push(ch);
      pIdx++;
    } else if (pattern[pIdx] === "@" && AMPM_CHAR.test(ch)) {
      chars.push(ch);
      pIdx++;
    }
  }
  return chars;
};
const rebuildFromChars = (validChars, pattern) => {
  let result = "";
  let charIdx = 0;
  for (let i = 0; i < pattern.length && charIdx < validChars.length; i++) {
    if (!isSlot(pattern[i])) continue;
    result += validChars[charIdx];
    charIdx++;
    let nextSlot = i + 1;
    while (nextSlot < pattern.length && !isSlot(pattern[nextSlot])) nextSlot++;
    const hasMoreChars = charIdx < validChars.length;
    const hasMoreSlots = nextSlot < pattern.length;
    if (hasMoreChars || hasMoreSlots) {
      for (let s = i + 1; s < nextSlot; s++) result += pattern[s];
    }
    if (hasMoreChars) i = nextSlot - 1;
  }
  return result;
};
const stripTrailingSeparators = (text) => {
  let end = text.length;
  while (end > 0 && !VALID_CHAR.test(text[end - 1])) end--;
  return text.slice(0, end);
};
const cursorSlotToDisplayPos = (result, cursorSlot, pattern, skipSeparators) => {
  if (cursorSlot <= 0) return 0;
  let slotsFound = 0;
  for (let i = 0; i < result.length; i++) {
    if (!VALID_CHAR.test(result[i])) continue;
    slotsFound++;
    if (slotsFound !== cursorSlot) continue;
    let pos = i + 1;
    if (skipSeparators) {
      while (pos < result.length && pos < pattern.length && !isSlot(pattern[pos])) {
        pos++;
      }
    }
    return pos;
  }
  return result.length;
};
const isValidForSlot = (ch, slotChar) => slotChar === "#" ? DIGIT.test(ch) : slotChar === "@" ? AMPM_CHAR.test(ch) : false;
const applyMask = (inputText, template, previousText = "", nativeCursorPos) => {
  const { pattern, hasAmPm } = template;
  if (!inputText) return { text: "", cursorPosition: 0 };
  const isDeleting = inputText.length < previousText.length;
  if (isDeleting && previousText.length >= pattern.length) {
    const delPos = findDeletionPoint(previousText, inputText);
    if (!hasDigitsAfter(previousText, delPos, pattern) || delPos >= pattern.length) ; else if (isSlot(pattern[delPos])) {
      return {
        text: previousText.substring(0, delPos) + "_" + previousText.substring(delPos + 1),
        cursorPosition: delPos
      };
    } else {
      return { text: previousText, cursorPosition: delPos };
    }
  }
  const totalSlots = [...pattern].filter(isSlot).length;
  const slotPatternPositions = [...pattern].reduce(
    (acc, ch, i) => isSlot(ch) ? [...acc, i] : acc,
    []
  );
  const cursorInInput = nativeCursorPos ?? inputText.length;
  const cursorSlot = countValidBefore(inputText, cursorInInput, hasAmPm);
  const prevChars = extractWithPattern(previousText, pattern);
  let validChars;
  const isSingleCharInsert = !isDeleting && prevChars.length >= totalSlots && inputText.length - previousText.length === 1;
  if (isSingleCharInsert) {
    const insertSlot = cursorSlot - 1;
    const typedChar = inputText[cursorInInput - 1]?.toUpperCase() ?? "";
    const slotChar = slotPatternPositions[insertSlot] != null ? pattern[slotPatternPositions[insertSlot]] : null;
    if (!slotChar || !isValidForSlot(typedChar, slotChar)) {
      return {
        text: previousText,
        cursorPosition: Math.min(cursorInInput, previousText.length)
      };
    }
    validChars = [...prevChars];
    validChars[insertSlot] = typedChar;
  } else {
    validChars = extractWithPattern(inputText, pattern).slice(0, totalSlots);
  }
  let result = rebuildFromChars(validChars, pattern);
  if (isDeleting) {
    result = stripTrailingSeparators(result);
  }
  if (hasAmPm && !isDeleting) {
    const ampmStart = pattern.indexOf("@");
    if (ampmStart >= 0 && result.length > ampmStart) {
      const ampmPortion = result.slice(ampmStart);
      if (ampmPortion === "A") result = result.slice(0, ampmStart) + "AM";
      else if (ampmPortion === "P") result = result.slice(0, ampmStart) + "PM";
    }
  }
  return {
    text: result,
    cursorPosition: cursorSlotToDisplayPos(
      result,
      cursorSlot,
      pattern,
      !isDeleting
    )
  };
};

const COLUMN_HEIGHT = 224;
const ITEM_HEIGHT = 32;
const PERIODS = ["AM", "PM"];
const SIZE_CONFIG = {
  small: {
    trigger: "h-8 md:h-7",
    input: "text-base md:text-xs",
    icon: "size-3.5"
  },
  medium: {
    trigger: "h-8",
    input: "text-base md:text-sm",
    icon: "size-4"
  },
  large: {
    trigger: "h-10",
    input: "text-base md:text-sm",
    icon: "size-5"
  }
};

pureDayjs.extend(customParseFormat);
const generateRange = (start, end, step = 1) => {
  const result = [];
  for (let i = start; i <= end; i += step) {
    result.push(i);
  }
  return result;
};
const to12Hour = (hour24) => {
  const period = hour24 >= 12 ? "PM" : "AM";
  const hour12 = hour24 % 12 || 12;
  return { hour12, period };
};
const to24Hour = (hour12, period) => {
  if (period === "AM") {
    return hour12 === 12 ? 0 : hour12;
  }
  return hour12 === 12 ? 12 : hour12 + 12;
};
const dateToTimeValue = (date) => ({
  hours: date.getHours(),
  minutes: date.getMinutes(),
  seconds: date.getSeconds()
});
const coerceToTimeValue = (value) => {
  if (!value) return null;
  if (value instanceof Date) return dateToTimeValue(value);
  const dayjsLike = value;
  if (typeof dayjsLike.hour === "function") {
    return {
      hours: dayjsLike.hour(),
      minutes: dayjsLike.minute(),
      seconds: dayjsLike.second?.() ?? 0
    };
  }
  if (typeof dayjsLike.toDate === "function") {
    return dateToTimeValue(dayjsLike.toDate());
  }
  const timeValue = value;
  return {
    hours: timeValue.hours,
    minutes: timeValue.minutes,
    seconds: timeValue.seconds ?? 0
  };
};
const timeValueEquals = (a, b) => {
  if (a === null && b === null) return true;
  if (!a || !b) return false;
  return a.hours === b.hours && a.minutes === b.minutes && (a.seconds ?? 0) === (b.seconds ?? 0);
};
const getDefaultTimeMaskFormat = (format, showSeconds) => {
  if (format === "12") return showSeconds ? "hh:mm:ss AA" : "hh:mm AA";
  return showSeconds ? "HH:mm:ss" : "HH:mm";
};
const getTimePlaceholder = (format, showSeconds, displayFormat) => displayFormat ?? getDefaultTimeMaskFormat(format, showSeconds);
const padNumber = (num, length = 2) => String(num).padStart(length, "0");
const formatTimeDisplay = (time, format, showSeconds) => {
  if (!time) return "";
  if (format === "12") {
    const { hour12, period } = to12Hour(time.hours);
    const parts2 = [padNumber(hour12), padNumber(time.minutes)];
    if (showSeconds) parts2.push(padNumber(time.seconds ?? 0));
    return `${parts2.join(":")} ${period}`;
  }
  const parts = [padNumber(time.hours), padNumber(time.minutes)];
  if (showSeconds) parts.push(padNumber(time.seconds ?? 0));
  return parts.join(":");
};
const parseTimeString = (input, format) => {
  const trimmed = input.trim();
  if (!trimmed) return null;
  if (format === "12") {
    const match2 = trimmed.match(
      /^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\s*(AM|PM)$/i
    );
    if (!match2) return null;
    const hour12 = parseInt(match2[1], 10);
    const minutes2 = parseInt(match2[2], 10);
    const seconds2 = match2[3] ? parseInt(match2[3], 10) : 0;
    const period = match2[4].toUpperCase();
    if (hour12 < 1 || hour12 > 12 || minutes2 > 59 || seconds2 > 59) return null;
    return { hours: to24Hour(hour12, period), minutes: minutes2, seconds: seconds2 };
  }
  const match = trimmed.match(/^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/);
  if (!match) return null;
  const hours = parseInt(match[1], 10);
  const minutes = parseInt(match[2], 10);
  const seconds = match[3] ? parseInt(match[3], 10) : 0;
  if (hours > 23 || minutes > 59 || seconds > 59) return null;
  return { hours, minutes, seconds };
};
const formatTimeWithPattern = (time, pattern) => {
  if (!time) return "";
  return pureDayjs("2000-01-01T00:00:00").hour(time.hours).minute(time.minutes).second(time.seconds ?? 0).format(pattern);
};
const parseTimeWithPattern = (input, pattern) => {
  const trimmed = input.trim();
  if (!trimmed) return null;
  const parsed = pureDayjs(trimmed, pattern, true);
  if (!parsed.isValid()) return null;
  return {
    hours: parsed.hour(),
    minutes: parsed.minute(),
    seconds: parsed.second()
  };
};
const timeValueToDayjs = (time) => {
  const h = padNumber(time.hours);
  const m = padNumber(time.minutes);
  const s = padNumber(time.seconds ?? 0);
  const today = dayjs(/* @__PURE__ */ new Date()).format("YYYY-MM-DD");
  return dayjs(`${today} ${h}:${m}:${s}`);
};

const TimeColumn = ({
  items,
  selected,
  onSelect,
  disabled = false,
  className,
  padStart = 2
}) => {
  const selectedRef = useRef(null);
  useEffect(() => {
    if (selectedRef.current) {
      selectedRef.current.scrollIntoView({
        block: "center",
        behavior: "smooth"
      });
    }
  }, [selected]);
  return /* @__PURE__ */ jsx(
    ScrollArea,
    {
      className: cn("w-16", className),
      style: { height: COLUMN_HEIGHT },
      children: /* @__PURE__ */ jsx(
        "div",
        {
          className: "flex flex-col items-center py-2",
          role: "listbox",
          "aria-label": "Time selection",
          children: items.map((item) => {
            const isSelected = item === selected;
            const displayValue = typeof item === "number" ? padNumber(item, padStart) : item;
            return /* @__PURE__ */ jsx(
              Button,
              {
                ref: isSelected ? selectedRef : void 0,
                variant: isSelected ? "default" : "ghost",
                size: "sm",
                role: "option",
                "aria-selected": isSelected,
                disabled,
                className: cn(
                  "w-12 justify-center font-mono text-sm",
                  !isSelected && "text-muted-foreground"
                ),
                style: { height: ITEM_HEIGHT },
                onClick: () => onSelect(item),
                children: displayValue
              },
              item
            );
          })
        }
      )
    }
  );
};
TimeColumn.displayName = "TimeColumn";

const TimePickerPanel = forwardRef(
  ({
    value,
    onChange,
    format = "24",
    showSeconds = false,
    minuteStep = 1,
    secondStep = 1,
    disabled = false,
    className
  }, ref) => {
    const getInitialTime = useCallback(
      () => ({
        hours: value?.hours ?? 0,
        minutes: value?.minutes ?? 0,
        seconds: value?.seconds ?? 0
      }),
      [value]
    );
    const [internalTime, setInternalTime] = useState(getInitialTime);
    useEffect(() => {
      setInternalTime(getInitialTime());
    }, [getInitialTime]);
    const hours = format === "12" ? generateRange(1, 12) : generateRange(0, 23);
    const minutes = generateRange(0, 59, minuteStep);
    const seconds = generateRange(0, 59, secondStep);
    const { hour12, period } = format === "12" ? to12Hour(internalTime.hours) : { hour12: internalTime.hours, period: "AM" };
    const handleHourSelect = (hourValue) => {
      const hour = Number(hourValue);
      const newHours = format === "12" ? to24Hour(hour, period) : hour;
      const newTime = { ...internalTime, hours: newHours };
      setInternalTime(newTime);
      onChange?.(newTime);
    };
    const handleMinuteSelect = (minuteValue) => {
      const newTime = { ...internalTime, minutes: Number(minuteValue) };
      setInternalTime(newTime);
      onChange?.(newTime);
    };
    const handleSecondSelect = (secondValue) => {
      const newTime = { ...internalTime, seconds: Number(secondValue) };
      setInternalTime(newTime);
      onChange?.(newTime);
    };
    const handlePeriodSelect = (newPeriod) => {
      const newHours = to24Hour(hour12, newPeriod);
      const newTime = { ...internalTime, hours: newHours };
      setInternalTime(newTime);
      onChange?.(newTime);
    };
    return /* @__PURE__ */ jsxs(
      "div",
      {
        ref,
        className: cn("flex", className),
        role: "group",
        "aria-label": "Time picker",
        children: [
          /* @__PURE__ */ jsx(
            TimeColumn,
            {
              items: hours,
              selected: format === "12" ? hour12 : internalTime.hours,
              onSelect: handleHourSelect,
              disabled
            }
          ),
          /* @__PURE__ */ jsx("div", { className: "border-inline-end border-border" }),
          /* @__PURE__ */ jsx(
            TimeColumn,
            {
              items: minutes,
              selected: internalTime.minutes,
              onSelect: handleMinuteSelect,
              disabled
            }
          ),
          showSeconds && /* @__PURE__ */ jsxs(Fragment, { children: [
            /* @__PURE__ */ jsx("div", { className: "border-inline-end border-border" }),
            /* @__PURE__ */ jsx(
              TimeColumn,
              {
                items: seconds,
                selected: internalTime.seconds ?? 0,
                onSelect: handleSecondSelect,
                disabled
              }
            )
          ] }),
          format === "12" && /* @__PURE__ */ jsxs(Fragment, { children: [
            /* @__PURE__ */ jsx("div", { className: "border-inline-end border-border" }),
            /* @__PURE__ */ jsx(
              TimeColumn,
              {
                items: [...PERIODS],
                selected: period,
                onSelect: handlePeriodSelect,
                disabled,
                padStart: 0
              }
            )
          ] })
        ]
      }
    );
  }
);
TimePickerPanel.displayName = "TimePickerPanel";

export { SIZE_CONFIG as S, TimePickerPanel as T, formatTimeDisplay as a, useOutsideClickClose as b, coerceToTimeValue as c, buildMaskTemplate as d, useCursorRestore as e, formatTimeWithPattern as f, getDefaultTimeMaskFormat as g, timeValueToDayjs as h, getTimePlaceholder as i, applyMask as j, parseTimeString as k, isFixedWidthFormat as l, buildRangeMaskTemplate as m, dateToTimeValue as n, parseTimeWithPattern as p, timeValueEquals as t, useControlledOpen as u };
//# sourceMappingURL=TimePickerPanel--KDX5QwS.js.map
