import { _objectSpread2 } from "../../_virtual/_@oxc-project_runtime@0.122.0/helpers/objectSpread2.js";
import PisellScrollView_default from "../PisellScrollView/index.js";
import useEngineContext from "../../hooks/useEngineContext.js";
import { getHorizontalAxisSlotCount, getRangeBounds, isInDisabledRanges, parseValue, pickReferenceDateContainingNow, snapToStepMinutes } from "./utils/index.js";
import { useTimeAxis } from "./hooks/useTimeAxis.js";
import { useRealtime } from "./hooks/useRealtime.js";
import { useDragInertia } from "./hooks/useDragInertia.js";
import { BackgroundLayer } from "./components/BackgroundLayer.js";
import { CursorLayer } from "./components/CursorLayer.js";
import { GestureLayer } from "./components/GestureLayer.js";
import { NowButton } from "./components/NowButton.js";
import { ScaleLayer } from "./components/ScaleLayer.js";
import locales_default from "./locales.js";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { locales } from "@pisell/utils";
import classNames from "classnames";
import dayjs from "dayjs";
import "./PisellTimeNavigator.less";
//#region src/components/pisellTimeNavigator/PisellTimeNavigator.tsx
/** 默认横向轨道高度（px），与 Less 中 `--pisell-time-nav-track-h` 回退值一致 */
const DEFAULT_HORIZONTAL_TRACK_HEIGHT_PX = 48;
/** 与 `CURRENT_TIME_GRADIENT_WIDTH_H_PX = CELL_SIZE_H * 1.7` 同比例，随格宽变化 */
const horizontalCurrentTimeGradientWidthPx = (cellPx) => Math.round(cellPx * 1.7);
/**
* PisellTimeNavigator：可拖动的时间导航轴，时间范围内浏览、选择、控制
*/
function PisellTimeNavigator(props) {
	var _context$engine;
	const { value: valueProp, onChange, defaultValue, referenceDate: referenceDateProp, realtime = false, range, stepMinutes = 15, majorStepMinutes = 60, cursorMode = "axis-moves", orientation = "horizontal", horizontalAxisCellSizePx, horizontalTrackHeightPx, disabledRanges, showNowButton = true, userInteractionLocked = false, snapToStep = false, inertia = false, businessHours, pastTimeFade, containerStyle, scaleStyle, cursorStyle, nowButtonStyle, className, style, locale, timeFormat = "HH:mm", formatTimeLabel, renderNowButton, onUserInteraction, densitySeries: densitySeriesProp, loadDensity: loadDensityProp } = props;
	const context = useEngineContext();
	locales.init(locales_default, (context === null || context === void 0 || (_context$engine = context.engine) === null || _context$engine === void 0 || (_context$engine = _context$engine.props) === null || _context$engine === void 0 ? void 0 : _context$engine.locale) || "en");
	/**
	* 锚定日历日：
	* - 显式传入 prop 时以 prop 为准（调用方可能在「看未来 / 过去某一天」的语义下指定）；
	* - 未传 prop 时，自动挑选「能让 dayjs() 落入营业窗口 [refDay + range.start, refDay + range.end]」
	*   的那一天，使得橙色「现在」高亮永远落在轴内。
	*   例：营业边界 16:00（24h 窗口），now = 中午 12:00 → 选昨天，窗口 [昨 16:00, 今 16:00] 包含 now；
	*       now = 下午 18:00 → 选今天，窗口 [今 16:00, 明 16:00] 包含 now。
	*/
	const referenceDateFromProp = useMemo(() => {
		if (referenceDateProp == null) return void 0;
		const d = parseValue(referenceDateProp, dayjs());
		return (d !== null && d !== void 0 ? d : dayjs()).startOf("day");
	}, [referenceDateProp]);
	const referenceDate = referenceDateFromProp !== null && referenceDateFromProp !== void 0 ? referenceDateFromProp : pickReferenceDateContainingNow(range, dayjs());
	const isHorizontal = orientation === "horizontal";
	/**
	* 横向轨道高度（px）；刻度层、密度 SVG、ScrollView 与轴 inline 高度对齐。
	* 竖向布局不使用本值（保持默认 48 不参与垂直轴样式）。
	*/
	const trackHeightPx = useMemo(() => {
		const raw = horizontalTrackHeightPx;
		if (raw == null || !Number.isFinite(Number(raw))) return DEFAULT_HORIZONTAL_TRACK_HEIGHT_PX;
		const n = Math.round(Number(raw));
		return Math.max(28, Math.min(96, n));
	}, [horizontalTrackHeightPx]);
	/**
	* 按自然日稳定依赖，避免无 referenceDate prop 时每次 render 新建 dayjs 触发重复 loadDensity。
	* 注：自动挑选的 referenceDate（可能为昨/明）也要带进 key，否则跨越营业边界后密度仍会停留在前一日。
	*/
	const referenceDayKey = referenceDate.format("YYYY-MM-DD");
	const [densityLoaded, setDensityLoaded] = useState();
	const densityReqId = useRef(0);
	/**
	* 仅当日历锚定日或 step 变化时清空密度折线；`loadDensity` 回调因上游 deps 变引用而变时
	* 保留上一帧数据直至新请求返回，避免预约页列表/高光周期性更新导致整轴「闪一下」。
	*/
	const densityAxisKeyRef = useRef(null);
	const densityGradientId = useMemo(() => `pisell-time-density-fill-${Math.random().toString(36).slice(2, 8)}`, []);
	useEffect(() => {
		const axisKey = `${referenceDayKey}|${stepMinutes}`;
		const axisKeyChanged = densityAxisKeyRef.current === null || densityAxisKeyRef.current !== axisKey;
		if (densitySeriesProp !== void 0) {
			densityAxisKeyRef.current = null;
			setDensityLoaded(void 0);
			return;
		}
		if (!loadDensityProp) {
			densityAxisKeyRef.current = null;
			setDensityLoaded(void 0);
			return;
		}
		densityAxisKeyRef.current = axisKey;
		const req = ++densityReqId.current;
		let cancelled = false;
		if (axisKeyChanged) setDensityLoaded(void 0);
		loadDensityProp({
			referenceDate: dayjs(referenceDayKey).startOf("day"),
			stepMinutes
		}).then((arr) => {
			if (cancelled || req !== densityReqId.current) return;
			setDensityLoaded(Array.isArray(arr) ? arr : []);
		}).catch(() => {
			if (cancelled || req !== densityReqId.current) return;
			setDensityLoaded(void 0);
		});
		return () => {
			cancelled = true;
		};
	}, [
		referenceDayKey,
		stepMinutes,
		loadDensityProp,
		densitySeriesProp
	]);
	const resolvedDensity = densitySeriesProp !== void 0 ? densitySeriesProp : densityLoaded;
	const isAxisMoves = cursorMode === "axis-moves";
	const [viewportSize, setViewportSize] = useState({
		w: 0,
		h: 0
	});
	/** 挂载和视口变化时同步尺寸（需在 cellSize 计算之前声明 state） */
	const viewportWrapRef = useRef(null);
	useEffect(() => {
		const el = viewportWrapRef.current;
		if (!el) return;
		const setSize = () => {
			const w = el.clientWidth;
			const h = el.clientHeight;
			setViewportSize((prev) => prev.w === w && prev.h === h ? prev : {
				w,
				h
			});
		};
		setSize();
		const ro = new ResizeObserver(setSize);
		ro.observe(el);
		return () => ro.disconnect();
	}, []);
	const horizontalSlotCount = useMemo(() => isHorizontal ? getHorizontalAxisSlotCount(range, referenceDate, stepMinutes) : 0, [
		isHorizontal,
		range === null || range === void 0 ? void 0 : range.start,
		range === null || range === void 0 ? void 0 : range.end,
		referenceDate,
		stepMinutes
	]);
	/** cursor-moves + 横向：整段 range 压进一条可视区域，等比例缩小格宽 */
	const cellSizePx = useMemo(() => {
		if (!isHorizontal) return 20;
		if (isAxisMoves) {
			const custom = horizontalAxisCellSizePx;
			if (custom != null && Number.isFinite(custom)) {
				const n = Number(custom);
				if (n > 0) return Math.max(32, Math.min(n, 200));
			}
			return 82;
		}
		const n = horizontalSlotCount;
		if (n <= 0) return 82;
		const vpW = Math.max(1, viewportSize.w || 400);
		const usable = Math.max(40, vpW - (showNowButton ? 56 : 0) - 16);
		const raw = Math.floor(usable / n);
		return Math.max(8, Math.min(82, raw));
	}, [
		isHorizontal,
		isAxisMoves,
		horizontalAxisCellSizePx,
		horizontalSlotCount,
		viewportSize.w,
		showNowButton
	]);
	const axis = useTimeAxis({
		range,
		stepMinutes,
		referenceDate,
		orientation,
		cellSizePx
	});
	const horizontalDensityCurve = useMemo(() => {
		if (!isHorizontal || !(resolvedDensity === null || resolvedDensity === void 0 ? void 0 : resolvedDensity.length)) return null;
		const ticksN = axis.ticks.length;
		const n = Math.min(resolvedDensity.length, ticksN);
		if (n < 2) return null;
		const slice = resolvedDensity.slice(0, n);
		const maxV = Math.max(...slice, 0);
		const minV = Math.min(...slice, 0);
		const span = Math.max(1, maxV - minV);
		/** 与默认 48px 轴配 46px 密度层一致：轨道高度减 2px 上下留白 */
		const h = Math.max(12, trackHeightPx - 2);
		const points = slice.map((v, i) => {
			/**
			* 用 `timeToOffset(ticks[i])` 而非 `i * cellSize + cellSize/2` 取 x：
			* head/tail partial 格存在时，每格宽度不再等于 `cellSize`，按索引等比相乘会跑偏。
			* `timeToOffset` 始终随时间线性映射（含两端 dotOffsetPx 留白），与刻度层视觉对齐。
			*/
			const x = axis.timeToOffset(axis.ticks[i]);
			/** 映射到 [0,1]：最低值贴底，最高值接近顶部，基本吃满密度层高度 */
			const t = (v - minV) / span;
			return {
				x,
				y: h - 1 - t * (h - 3)
			};
		});
		const first = points[0];
		const last = points[points.length - 1];
		let linePath = `M ${first.x} ${first.y}`;
		for (let i = 0; i < points.length - 1; i += 1) {
			const p0 = points[i];
			const p1 = points[i + 1];
			const cx = (p0.x + p1.x) / 2;
			const cy = (p0.y + p1.y) / 2;
			linePath += ` Q ${p0.x} ${p0.y} ${cx} ${cy}`;
		}
		linePath += ` T ${last.x} ${last.y}`;
		const areaPath = `${linePath} L ${last.x} ${h} L ${first.x} ${h} Z`;
		return {
			points,
			linePath,
			areaPath,
			h,
			w: axis.totalLengthPx
		};
	}, [
		isHorizontal,
		resolvedDensity,
		axis.ticks,
		axis.timeToOffset,
		axis.totalLengthPx,
		cellSizePx,
		trackHeightPx
	]);
	const isControlled = valueProp !== void 0;
	const [internalValue, setInternalValue] = useState(() => {
		return parseValue(valueProp !== null && valueProp !== void 0 ? valueProp : defaultValue, referenceDate);
	});
	const value = isControlled ? valueProp != null ? parseValue(valueProp, referenceDate) : null : internalValue;
	const effectiveValue = value !== null && value !== void 0 ? value : axis.rangeStart;
	const scrollRef = useRef(null);
	/** 标记当前是否为「程序性滚动到中心」：其触发的 onScrollEnd 不再做时间判定 */
	const scrollToCenterInProgressRef = useRef(false);
	/** 标记 value 是否由滚动/松手更新：若是则不再根据 value 做「滚动到中心」 */
	const valueUpdatedByScrollRef = useRef(false);
	/** 手指是否仍按在滚动区域上；仅当完全松手后才执行吸附 */
	const isPointerDownRef = useRef(false);
	const [scrollPosition, setScrollPosition] = useState({
		scrollLeft: 0,
		scrollTop: 0
	});
	/** 每分钟更新一次，使当前时间线/渐变块随真实时间移动 */
	const [, setNowTick] = useState(0);
	useEffect(() => {
		const id = setInterval(() => setNowTick((n) => n + 1), 60 * 1e3);
		return () => clearInterval(id);
	}, []);
	/** cursor-moves：拖指针时禁止程序性滚动抢焦点 */
	const cursorDragActiveRef = useRef(false);
	/**
	* 拖拽起点 + 缩放系数。
	* `pxPerClient` 把 `clientX` 的屏幕 CSS 像素换算成轴内本地像素（offsetPx 的单位）。
	* 没有变换时为 1；当祖先元素带有 `transform: scale(n)` 或浏览器在 iPad 等设备上对页面整体做了
	* 视觉缩放时，`getBoundingClientRect().width / offsetWidth` 会反映出渲染缩放系数 n。
	* 不做这层换算的话，手指走的屏幕距离会被原样累加到 offsetPx 上，但光标视觉位置经过 scale(n) 后
	* 只走 n*delta，于是用户感知"拖了 10 小时但时间只走了 9 小时"（典型 9/10 ≈ 0.9 倍 scale）。
	*/
	const cursorDragBaseRef = useRef({
		startClient: 0,
		startOffset: 0,
		pxPerClient: 1
	});
	/**
	* 计算 viewportWrapRef 当前的渲染缩放系数（screenPx → 本地 px 倍率）。
	* 若元素未挂载或宽度异常则返回 1，永不放大放小本意外的 delta。
	*/
	const measureWrapPxPerClient = useCallback(() => {
		const el = viewportWrapRef.current;
		if (!el) return 1;
		const rect = el.getBoundingClientRect();
		const layout = isHorizontal ? el.offsetWidth : el.offsetHeight;
		const rendered = isHorizontal ? rect.width : rect.height;
		if (!layout || !rendered) return 1;
		const scale = rendered / layout;
		if (!Number.isFinite(scale) || scale <= .01) return 1;
		return 1 / scale;
	}, [isHorizontal]);
	/**
	* 指针气泡放大反馈：仅 down/up/cancel 各触发一次重渲染（move 事件不参与），
	* 拖拽过程中不会引起轴体重排。
	*/
	const [isCursorDragging, setIsCursorDragging] = useState(false);
	/**
	* 拖拽性能：受控模式下 onChange 会让父级（如 PisellReservation）整页重渲，
	* 触控环境下尤其明显（手指仍按着但光标卡帧）。
	*
	* 解决策略 = 「视觉本地 + onChange 提交一次」：
	* - 拖拽过程中只更新本地 dragVisualOffsetPx（轻量 state，仅本组件重渲），
	*   光标和气泡跟着这个 offset 移动，跟手且廉价；
	* - 不调用 onChange / setInternalValue，父级和兄弟组件完全不知道在拖；
	* - 松手时清空本地 offset 并一次性 applyOffsetToNavigatorValue(_, true)，
	*   走 snap → onChange → 父级 setState 的正常路径。React 18 自动批处理，
	*   父级 valueProp 更新与本地 dragVisualOffsetPx 清零会合并成一次 render，
	*   不会出现「松手 → 旧值 → 新值」的回闪。
	*
	* 同帧多次 move 用 rAF 再折叠一层，应对高频鼠标 / 高刷设备。
	*/
	const [dragVisualOffsetPx, setDragVisualOffsetPx] = useState(null);
	const pendingDragOffsetRef = useRef(null);
	const dragRafIdRef = useRef(null);
	const cancelPendingDragFrame = useCallback(() => {
		if (dragRafIdRef.current != null) {
			cancelAnimationFrame(dragRafIdRef.current);
			dragRafIdRef.current = null;
		}
		pendingDragOffsetRef.current = null;
	}, []);
	useEffect(() => cancelPendingDragFrame, [cancelPendingDragFrame]);
	const getScrollContainer = useCallback(() => {
		var _scrollRef$current$ge, _scrollRef$current, _scrollRef$current$ge2;
		return (_scrollRef$current$ge = (_scrollRef$current = scrollRef.current) === null || _scrollRef$current === void 0 || (_scrollRef$current$ge2 = _scrollRef$current.getScrollContainer) === null || _scrollRef$current$ge2 === void 0 ? void 0 : _scrollRef$current$ge2.call(_scrollRef$current)) !== null && _scrollRef$current$ge !== void 0 ? _scrollRef$current$ge : null;
	}, []);
	const inertiaHandlers = useDragInertia({
		enabled: inertia && !userInteractionLocked,
		orientation,
		getScrollContainer
	});
	/** 横向：点位于每格中心 cellSizePx/2；竖向：对齐整点上面的粗线（格顶），dotOffsetPx=0 */
	/** 视口未量到前用默认值，避免首帧 axisStartMargin 为 0 导致错位 */
	const effectiveViewportW = viewportSize.w || 400;
	const effectiveViewportH = viewportSize.h || 300;
	const halfViewportW = effectiveViewportW / 2;
	const halfViewportH = effectiveViewportH / 2;
	const dotOffsetPx = isHorizontal ? cellSizePx / 2 : 0;
	/** cursor-moves 横向：左右小边距，整轴落在视口内；axis-moves 保持半屏留白可滚动 */
	const axisStartMarginW = isHorizontal && !isAxisMoves ? 8 : Math.max(0, halfViewportW - dotOffsetPx);
	const axisStartMarginH = Math.max(0, halfViewportH - dotOffsetPx);
	/**
	* 横向 axis-wrap：无 Now 时用 width:100% + minWidth:自然内容宽，铺满滚动视口；内容更长时仍可横向滚动。
	* 依赖 effectiveViewportW（须在 viewportSize 之后计算）。
	*/
	const horizontalAxisWrapStyle = useMemo(() => {
		if (!isHorizontal) return null;
		const minHeight = trackHeightPx;
		if (isAxisMoves) {
			const natural = axis.totalLengthPx + (effectiveViewportW - cellSizePx);
			if (!showNowButton) return {
				width: "100%",
				minWidth: natural,
				minHeight,
				boxSizing: "border-box"
			};
			return {
				width: natural,
				minHeight
			};
		}
		const natural = 8 + axis.totalLengthPx + 8;
		if (!showNowButton) return {
			width: "100%",
			minWidth: natural,
			minHeight,
			boxSizing: "border-box"
		};
		return {
			width: natural,
			minHeight
		};
	}, [
		isHorizontal,
		isAxisMoves,
		axis.totalLengthPx,
		effectiveViewportW,
		cellSizePx,
		showNowButton,
		trackHeightPx
	]);
	/** 拖动中：用连续映射按 1 分钟步长显示时间（不吸附到 stepMinutes） */
	const handleScroll = useCallback((data) => {
		/** 程序性滚动到中心（如点 Now）过程中不更新任何状态，避免时间轴再动、value 被覆盖 */
		if (scrollToCenterInProgressRef.current) return;
		setScrollPosition({
			scrollLeft: data.scrollLeft,
			scrollTop: data.scrollTop
		});
		setViewportSize({
			w: data.clientWidth,
			h: data.clientHeight
		});
		if (userInteractionLocked) return;
		if (!isAxisMoves || data.clientWidth <= 0 && data.clientHeight <= 0) return;
		const halfW = data.clientWidth / 2;
		const halfH = data.clientHeight / 2;
		const hasPadding = isHorizontal ? effectiveViewportW > 0 : effectiveViewportH > 0;
		const centerOffset = isHorizontal ? hasPadding ? data.scrollLeft + dotOffsetPx : data.scrollLeft + halfW : hasPadding ? data.scrollTop + dotOffsetPx : data.scrollTop + halfH;
		const t = axis.offsetToTimeContinuous(centerOffset);
		if (!t) return;
		const tMinute = t.startOf("minute");
		if (isInDisabledRanges(tMinute, disabledRanges)) return;
		valueUpdatedByScrollRef.current = true;
		if (!isControlled) setInternalValue(tMinute);
		onChange === null || onChange === void 0 || onChange(tMinute);
	}, [
		isAxisMoves,
		isHorizontal,
		isControlled,
		axis.offsetToTimeContinuous,
		disabledRanges,
		onChange,
		effectiveViewportW,
		effectiveViewportH,
		dotOffsetPx,
		userInteractionLocked
	]);
	/** 根据当前视口中心吸附到 0/15/30/45 分钟并更新 value；由松手后的 scrollEnd 或 pointerUp 调用 */
	const performSnapFromScrollPosition = useCallback((scrollLeft, scrollTop, clientWidth, clientHeight) => {
		if (!isAxisMoves || clientWidth <= 0 && clientHeight <= 0) return;
		valueUpdatedByScrollRef.current = false;
		const hasPadding = isHorizontal ? effectiveViewportW > 0 : effectiveViewportH > 0;
		const centerOffset = isHorizontal ? hasPadding ? scrollLeft + dotOffsetPx : scrollLeft + clientWidth / 2 : hasPadding ? scrollTop + dotOffsetPx : scrollTop + clientHeight / 2;
		const t = axis.offsetToTimeContinuous(centerOffset);
		if (!t) return;
		const snapped = snapToStep ? snapToStepMinutes(t, stepMinutes) : t;
		if (isInDisabledRanges(snapped, disabledRanges)) return;
		if (!isControlled) setInternalValue(snapped);
		onChange === null || onChange === void 0 || onChange(snapped);
	}, [
		isAxisMoves,
		isHorizontal,
		isControlled,
		axis.offsetToTimeContinuous,
		snapToStep,
		stepMinutes,
		disabledRanges,
		onChange,
		effectiveViewportW,
		effectiveViewportH,
		dotOffsetPx
	]);
	/** 程序性滚动结束后延迟清除标记，避免同一次滚动触发的后续 scrollEnd 误走吸附（导致轴动、值被改） */
	const scrollEndClearTimeoutRef = useRef(null);
	/** 滚动结束：仅当手指已松开时才吸附（避免拖动中停顿就吸附）；若仍按着则等 pointerUp 再吸附 */
	const handleScrollEnd = useCallback((data) => {
		if (userInteractionLocked) return;
		if (scrollToCenterInProgressRef.current) {
			if (scrollEndClearTimeoutRef.current) clearTimeout(scrollEndClearTimeoutRef.current);
			/** 不立即清除：程序性滚动的 scrollEnd 会在动画结束时才触发（可能数百 ms 后），延迟清除才能跳过那次吸附 */
			scrollEndClearTimeoutRef.current = setTimeout(() => {
				scrollToCenterInProgressRef.current = false;
				scrollEndClearTimeoutRef.current = null;
			}, 500);
			return;
		}
		if (isPointerDownRef.current) return;
		performSnapFromScrollPosition(data.scrollLeft, data.scrollTop, data.clientWidth, data.clientHeight);
	}, [performSnapFromScrollPosition, userInteractionLocked]);
	/** 手指松开时若尚未吸附（例如按住不动再松手），用当前滚动位置做一次吸附；点 Now 按钮松手不吸附 */
	const handlePointerUp = useCallback((e) => {
		var _e$target, _closest, _scrollRef$current2, _scrollRef$current2$g;
		isPointerDownRef.current = false;
		if (userInteractionLocked) return;
		if ((_e$target = e.target) === null || _e$target === void 0 || (_closest = _e$target.closest) === null || _closest === void 0 ? void 0 : _closest.call(_e$target, ".pisell-time-navigator-now")) return;
		if (!isAxisMoves) return;
		const container = (_scrollRef$current2 = scrollRef.current) === null || _scrollRef$current2 === void 0 || (_scrollRef$current2$g = _scrollRef$current2.getScrollContainer) === null || _scrollRef$current2$g === void 0 ? void 0 : _scrollRef$current2$g.call(_scrollRef$current2);
		if (!container) return;
		performSnapFromScrollPosition(container.scrollLeft, container.scrollTop, container.clientWidth, container.clientHeight);
	}, [
		isAxisMoves,
		performSnapFromScrollPosition,
		userInteractionLocked
	]);
	const snapFn = useCallback((t) => snapToStepMinutes(t, stepMinutes), [stepMinutes]);
	const isInDisabled = useCallback((t) => isInDisabledRanges(t, disabledRanges), [disabledRanges]);
	const handleChange = useCallback((next) => {
		if (isInDisabled(next)) return;
		if (!isControlled) setInternalValue(next);
		onChange === null || onChange === void 0 || onChange(next);
	}, [
		isControlled,
		onChange,
		isInDisabled
	]);
	useRealtime(realtime, () => {
		const wall = dayjs().startOf("minute");
		const [rangeStart, rangeEnd] = getRangeBounds(range, referenceDate);
		/** 跨日营业：用真实此刻落在 [rangeStart, rangeEnd] 内，而非仅 reference 当日钟面 */
		const clamped = wall.isBefore(rangeStart) ? rangeStart : wall.isAfter(rangeEnd) ? rangeEnd : wall;
		const snapped = snapToStep ? snapFn(clamped) : clamped;
		if (!isInDisabled(snapped)) handleChange(snapped);
	});
	useEffect(() => {
		if (!isControlled && valueProp !== void 0) setInternalValue(parseValue(valueProp, referenceDate));
	}, [
		isControlled,
		valueProp,
		referenceDate
	]);
	useEffect(() => {
		if (isControlled) return;
		setInternalValue((prev) => {
			if (!prev) return parseValue(defaultValue, referenceDate);
			return referenceDate.hour(prev.hour()).minute(prev.minute()).second(0).millisecond(0);
		});
	}, [
		referenceDate.valueOf(),
		isControlled,
		defaultValue
	]);
	const cursorOffsetPx = axis.timeToOffset(effectiveValue);
	/**
	* 拖拽视觉态：当用户正在拖动时，dragVisualOffsetPx 记录最新本地 offset，
	* 光标和拖拽气泡都用 visualCursorOffsetPx / visualValue，本组件自己消化高频更新；
	* 父级直到松手才会通过 onChange 收到一次最终值（带 snap）。
	*/
	const visualCursorOffsetPx = dragVisualOffsetPx !== null && dragVisualOffsetPx !== void 0 ? dragVisualOffsetPx : cursorOffsetPx;
	const visualValue = useMemo(() => {
		var _axis$offsetToTimeCon;
		if (dragVisualOffsetPx == null) return effectiveValue;
		return (_axis$offsetToTimeCon = axis.offsetToTimeContinuous(dragVisualOffsetPx)) !== null && _axis$offsetToTimeCon !== void 0 ? _axis$offsetToTimeCon : effectiveValue;
	}, [
		dragVisualOffsetPx,
		axis.offsetToTimeContinuous,
		effectiveValue
	]);
	/** 刻度「现在线」：真实系统时刻落在轴区间内时绘制（含跨日营业次日凌晨） */
	const wallAligned = dayjs().startOf("minute");
	const currentTimeOffsetPx = !wallAligned.isBefore(axis.rangeStart) && !wallAligned.isAfter(axis.rangeEnd) ? axis.timeToOffset(wallAligned) : void 0;
	/** 游标/「现在」/拖拽气泡共用：支持预约顶栏「次日 HH:mm」等自定义文案 */
	const resolveTimeLabel = useCallback((t) => {
		if (formatTimeLabel) return formatTimeLabel(t);
		return locale ? t.locale(locale).format(timeFormat) : t.format(timeFormat);
	}, [
		formatTimeLabel,
		locale,
		timeFormat
	]);
	/** 横向轴「此刻」橙区：宽度按格宽 ×1.7，右缘对齐当前时刻 */
	const horizontalCurrentTimeHighlightStyle = useMemo(() => {
		if (!isHorizontal || currentTimeOffsetPx == null || currentTimeOffsetPx < 0) return null;
		const gradW = horizontalCurrentTimeGradientWidthPx(cellSizePx);
		const w = Math.min(gradW, currentTimeOffsetPx);
		return {
			left: axisStartMarginW + Math.max(0, currentTimeOffsetPx - w),
			top: 0,
			width: w,
			height: trackHeightPx
		};
	}, [
		isHorizontal,
		currentTimeOffsetPx,
		axisStartMarginW,
		cellSizePx,
		trackHeightPx
	]);
	const SCROLL_SYNC_THRESHOLD_PX = 2;
	const hasScrollPadding = isHorizontal ? effectiveViewportW > 0 : effectiveViewportH > 0;
	/**
	* 竖向：上留白用 axis marginTop，下留白用 axis marginBottom（均为半屏）
	* axis-wrap 高度 = marginTop + 轴内容 + marginBottom
	*/
	const verticalTailPx = effectiveViewportH / 2;
	const verticalWrapHeight = axisStartMarginH + axis.totalLengthPx + verticalTailPx;
	const maxScrollPx = isHorizontal ? Math.max(0, axis.totalLengthPx - cellSizePx) : Math.max(0, verticalWrapHeight - effectiveViewportH);
	useEffect(() => {
		var _scrollRef$current4, _scrollRef$current4$g;
		/** 横向 cursor-moves：整段 range 已压进视口，禁止程序性横滚 */
		if (!isAxisMoves && isHorizontal) {
			var _scrollRef$current3, _scrollRef$current3$g;
			const container = (_scrollRef$current3 = scrollRef.current) === null || _scrollRef$current3 === void 0 || (_scrollRef$current3$g = _scrollRef$current3.getScrollContainer) === null || _scrollRef$current3$g === void 0 ? void 0 : _scrollRef$current3$g.call(_scrollRef$current3);
			if (container && container.scrollLeft !== 0) container.scrollTo({
				left: 0,
				behavior: "auto"
			});
			return;
		}
		if (!isAxisMoves && cursorDragActiveRef.current) return;
		if (valueUpdatedByScrollRef.current) {
			valueUpdatedByScrollRef.current = false;
			return;
		}
		const container = (_scrollRef$current4 = scrollRef.current) === null || _scrollRef$current4 === void 0 || (_scrollRef$current4$g = _scrollRef$current4.getScrollContainer) === null || _scrollRef$current4$g === void 0 ? void 0 : _scrollRef$current4$g.call(_scrollRef$current4);
		if (!container) return;
		const viewportPx = isHorizontal ? container.clientWidth : container.clientHeight;
		const targetScroll = hasScrollPadding ? Math.max(0, Math.min(cursorOffsetPx - dotOffsetPx, maxScrollPx)) : Math.max(0, Math.min(cursorOffsetPx - viewportPx / 2, Math.max(0, axis.totalLengthPx - viewportPx)));
		const currentScroll = isHorizontal ? container.scrollLeft : container.scrollTop;
		if (Math.abs(currentScroll - targetScroll) <= SCROLL_SYNC_THRESHOLD_PX) return;
		scrollToCenterInProgressRef.current = true;
		if (isHorizontal) container.scrollTo({
			left: targetScroll,
			behavior: "smooth"
		});
		else container.scrollTo({
			top: targetScroll,
			behavior: "smooth"
		});
	}, [
		effectiveValue.valueOf(),
		isHorizontal,
		cursorOffsetPx,
		maxScrollPx,
		hasScrollPadding,
		dotOffsetPx,
		cellSizePx,
		axis.totalLengthPx,
		isAxisMoves
	]);
	const scrollToCenterTime = useCallback((t) => {
		const container = getScrollContainer();
		if (!container) return;
		const offset = axis.timeToOffset(t);
		const viewportPx = isHorizontal ? container.clientWidth : container.clientHeight;
		const maxScroll = hasScrollPadding ? maxScrollPx : Math.max(0, axis.totalLengthPx - viewportPx);
		const target = hasScrollPadding ? Math.max(0, Math.min(offset - dotOffsetPx, maxScrollPx)) : Math.max(0, Math.min(offset - viewportPx / 2, maxScroll));
		scrollToCenterInProgressRef.current = true;
		container.scrollTo({
			left: isHorizontal ? target : 0,
			top: isHorizontal ? 0 : target,
			behavior: "smooth"
		});
	}, [
		getScrollContainer,
		axis.timeToOffset,
		axis.totalLengthPx,
		isHorizontal,
		hasScrollPadding,
		dotOffsetPx,
		maxScrollPx
	]);
	const handleSelectTime = useCallback((t) => {
		handleChange(t);
		if (isAxisMoves) scrollToCenterTime(t);
	}, [
		handleChange,
		isAxisMoves,
		scrollToCenterTime
	]);
	/** 点 Now：系统当前时刻映射到锚定日，再 clamp 到 range */
	const handleNow = useCallback(() => {
		valueUpdatedByScrollRef.current = false;
		const wall = dayjs().startOf("minute");
		const [rangeStart, rangeEnd] = getRangeBounds(range, referenceDate);
		handleChange(wall.isBefore(rangeStart) ? rangeStart : wall.isAfter(rangeEnd) ? rangeEnd : wall);
	}, [
		range,
		referenceDate,
		handleChange
	]);
	const applyOffsetToNavigatorValue = useCallback((offsetPx, snap) => {
		const maxO = Math.max(0, axis.totalLengthPx - .001);
		const nextOff = Math.max(0, Math.min(offsetPx, maxO));
		const t = axis.offsetToTimeContinuous(nextOff);
		if (!t) return;
		const out = snapToStep ? snap ? snapToStepMinutes(t, stepMinutes) : t.startOf("minute") : t;
		if (isInDisabledRanges(out, disabledRanges)) return;
		if (!isControlled) setInternalValue(out);
		onChange === null || onChange === void 0 || onChange(out);
	}, [
		axis.totalLengthPx,
		axis.offsetToTimeContinuous,
		snapToStep,
		stepMinutes,
		disabledRanges,
		isControlled,
		onChange
	]);
	const handleCursorDragPointerDown = useCallback((e) => {
		if (userInteractionLocked || isAxisMoves) return;
		e.preventDefault();
		e.stopPropagation();
		onUserInteraction === null || onUserInteraction === void 0 || onUserInteraction();
		cursorDragActiveRef.current = true;
		cursorDragBaseRef.current = {
			startClient: isHorizontal ? e.clientX : e.clientY,
			startOffset: cursorOffsetPx,
			pxPerClient: measureWrapPxPerClient()
		};
		valueUpdatedByScrollRef.current = true;
		setIsCursorDragging(true);
		e.currentTarget.setPointerCapture(e.pointerId);
	}, [
		userInteractionLocked,
		isAxisMoves,
		isHorizontal,
		cursorOffsetPx,
		onUserInteraction,
		measureWrapPxPerClient
	]);
	const handleCursorDragPointerMove = useCallback((e) => {
		if (!cursorDragActiveRef.current) return;
		/**
		* 把 clientX 的屏幕 CSS 像素 delta 折算成轴本地像素。
		* 没有 CSS 缩放时 pxPerClient=1；祖先有 transform: scale(0.9) 等情况下，
		* 1 屏幕 px 对应 1/0.9 ≈ 1.11 本地 px，否则 offsetPx 走得不够，
		* 表现为"光标跟不上手指、时间轴比手指走得近"。
		*/
		const delta = ((isHorizontal ? e.clientX : e.clientY) - cursorDragBaseRef.current.startClient) * (cursorDragBaseRef.current.pxPerClient || 1);
		/**
		* rAF 节流 + 仅更新本地视觉 offset：
		* - 不调用 onChange，避免父级（reservation 主页 / 楼层图 / 排程带等）
		*   每帧重渲，触控环境下尤其明显；
		* - 只更新本地 dragVisualOffsetPx，光标 + 拖拽气泡通过它定位，
		*   再叠加 rAF 把同帧多次 move 合并成一次 setState。
		* 真正提交 onChange 只发生在 pointerUp。
		*/
		pendingDragOffsetRef.current = cursorDragBaseRef.current.startOffset + delta;
		if (dragRafIdRef.current != null) return;
		dragRafIdRef.current = requestAnimationFrame(() => {
			dragRafIdRef.current = null;
			const pending = pendingDragOffsetRef.current;
			pendingDragOffsetRef.current = null;
			if (pending == null) return;
			if (!cursorDragActiveRef.current) return;
			const maxO = Math.max(0, axis.totalLengthPx - .001);
			setDragVisualOffsetPx(Math.max(0, Math.min(pending, maxO)));
		});
	}, [isHorizontal, axis.totalLengthPx]);
	const handleCursorDragPointerUp = useCallback((e) => {
		if (!cursorDragActiveRef.current) return;
		cursorDragActiveRef.current = false;
		setIsCursorDragging(false);
		try {
			e.currentTarget.releasePointerCapture(e.pointerId);
		} catch (_unused) {}
		const delta = ((isHorizontal ? e.clientX : e.clientY) - cursorDragBaseRef.current.startClient) * (cursorDragBaseRef.current.pxPerClient || 1);
		const nextOff = cursorDragBaseRef.current.startOffset + delta;
		valueUpdatedByScrollRef.current = false;
		/**
		* 松手时：
		* 1) 取消未消费的 rAF 帧（已被最终 offset 取代）；
		* 2) 清空本地视觉 offset 并 apply 最终值（snap=true）。
		*    React 18 自动批处理：setDragVisualOffsetPx(null) 与父级响应
		*    onChange 后回填的 valueProp 会合并成同一次渲染，不会出现
		*    「松手 → 旧值 → 新值」的回闪。
		*/
		cancelPendingDragFrame();
		setDragVisualOffsetPx(null);
		applyOffsetToNavigatorValue(nextOff, true);
	}, [
		isHorizontal,
		applyOffsetToNavigatorValue,
		cancelPendingDragFrame
	]);
	const businessHoursPx = useMemo(() => {
		if (!businessHours) return void 0;
		const [s, e] = getRangeBounds(businessHours, referenceDate);
		return [axis.timeToOffset(s), axis.timeToOffset(e)];
	}, [
		businessHours,
		referenceDate,
		axis.timeToOffset
	]);
	const scrollConfig = useMemo(() => ({
		show: false,
		type: isHorizontal ? "lrArrows" : "tbArrows",
		scrollDistance: cellSizePx
	}), [isHorizontal, cellSizePx]);
	return /* @__PURE__ */ React.createElement("div", {
		className: classNames("pisell-time-navigator", `pisell-time-navigator--${orientation}`, userInteractionLocked && "pisell-time-navigator--interaction-locked", className),
		style: isHorizontal ? _objectSpread2(_objectSpread2({}, style !== null && style !== void 0 ? style : {}), {}, { ["--pisell-time-nav-track-h"]: `${trackHeightPx}px` }) : _objectSpread2({
			height: "100%",
			minHeight: 200
		}, style !== null && style !== void 0 ? style : {})
	}, /* @__PURE__ */ React.createElement("div", {
		ref: viewportWrapRef,
		className: "pisell-time-navigator-viewport-wrap",
		style: isHorizontal ? void 0 : {
			height: "100%",
			minHeight: 200,
			maxHeight: 800
		},
		onPointerDownCapture: () => {
			isPointerDownRef.current = true;
		},
		onPointerUpCapture: handlePointerUp,
		onPointerLeaveCapture: handlePointerUp
	}, /* @__PURE__ */ React.createElement("div", { className: classNames("pisell-time-navigator-scroll-host", userInteractionLocked && "pisell-time-navigator-scroll-host--interaction-locked") }, /* @__PURE__ */ React.createElement(PisellScrollView_default, {
		ref: scrollRef,
		width: "100%",
		height: "100%",
		overflow: isHorizontal ? "x" : "y",
		showScrollButtons: false,
		scrollbarStyle: "none",
		scrollActionsConfig: scrollConfig,
		onScroll: handleScroll,
		onScrollEnd: handleScrollEnd,
		style: _objectSpread2(_objectSpread2({}, isHorizontal ? { height: trackHeightPx } : {
			width: 54,
			minHeight: 200,
			height: "100%",
			maxHeight: 800
		}), containerStyle)
	}, /* @__PURE__ */ React.createElement(GestureLayer, {
		orientation,
		cellSizePx,
		axisStartMarginPx: isHorizontal ? axisStartMarginW : axisStartMarginH,
		offsetToTime: axis.offsetToTime,
		offsetToTimeContinuous: axis.offsetToTimeContinuous,
		onSelectTime: handleSelectTime,
		isInDisabledRanges: isInDisabled,
		snapToStep,
		stepMinutes,
		snapFn,
		scrollPosition,
		viewportSize,
		onUserInteraction,
		onPointerDown: inertiaHandlers.onPointerDown,
		onPointerMove: inertiaHandlers.onPointerMove,
		onPointerUp: inertiaHandlers.onPointerUp,
		onPointerLeave: inertiaHandlers.onPointerUp
	}, /* @__PURE__ */ React.createElement("div", {
		className: "pisell-time-navigator-axis-wrap",
		style: isHorizontal ? horizontalAxisWrapStyle !== null && horizontalAxisWrapStyle !== void 0 ? horizontalAxisWrapStyle : { minHeight: trackHeightPx } : { width: "100%" }
	}, horizontalCurrentTimeHighlightStyle && /* @__PURE__ */ React.createElement("div", {
		className: "pisell-time-navigator-current-gradient pisell-time-navigator-current-gradient--horizontal",
		style: horizontalCurrentTimeHighlightStyle,
		"aria-hidden": true
	}), /* @__PURE__ */ React.createElement("div", {
		className: classNames("pisell-time-navigator-axis", `pisell-time-navigator-axis--${orientation}`),
		"data-time-navigator-axis": true,
		style: isHorizontal ? {
			width: axis.totalLengthPx,
			height: trackHeightPx,
			marginLeft: axisStartMarginW
		} : {
			height: axis.totalLengthPx,
			width: "100%",
			marginTop: axisStartMarginH,
			marginBottom: verticalTailPx
		}
	}, /* @__PURE__ */ React.createElement(BackgroundLayer, {
		orientation,
		pastTimeFade,
		businessHoursPx
	}), horizontalDensityCurve ? /* @__PURE__ */ React.createElement("svg", {
		className: "pisell-time-navigator-density-layer",
		width: horizontalDensityCurve.w,
		height: horizontalDensityCurve.h,
		viewBox: `0 0 ${horizontalDensityCurve.w} ${horizontalDensityCurve.h}`,
		preserveAspectRatio: "none",
		"aria-hidden": true
	}, /* @__PURE__ */ React.createElement("defs", null, /* @__PURE__ */ React.createElement("linearGradient", {
		id: densityGradientId,
		x1: "0",
		y1: "0",
		x2: "0",
		y2: "1"
	}, /* @__PURE__ */ React.createElement("stop", {
		offset: "0%",
		stopColor: "#ff9c66",
		stopOpacity: "0.32"
	}), /* @__PURE__ */ React.createElement("stop", {
		offset: "100%",
		stopColor: "#ff9c66",
		stopOpacity: "0.02"
	}))), /* @__PURE__ */ React.createElement("path", {
		d: horizontalDensityCurve.areaPath,
		fill: `url(#${densityGradientId})`
	}), /* @__PURE__ */ React.createElement("path", {
		d: horizontalDensityCurve.linePath,
		fill: "none",
		stroke: "#ff8a4a",
		strokeWidth: 1.8,
		vectorEffect: "non-scaling-stroke"
	}), horizontalDensityCurve.points.map((p, i) => /* @__PURE__ */ React.createElement("circle", {
		key: i,
		cx: p.x,
		cy: p.y,
		r: 1.4,
		fill: "#ff8a4a"
	}))) : null, /* @__PURE__ */ React.createElement(ScaleLayer, {
		orientation,
		ticks: axis.ticks,
		stepMinutes,
		majorStepMinutes,
		timeFormat,
		locale,
		style: scaleStyle,
		horizontalCellSizePx: isHorizontal ? cellSizePx : void 0,
		cellWidthsPx: isHorizontal ? axis.cellWidthsPx : void 0,
		leadingPaddingPx: isHorizontal ? dotOffsetPx : void 0,
		trailingPaddingPx: isHorizontal ? dotOffsetPx : void 0
	}), isHorizontal && currentTimeOffsetPx != null && currentTimeOffsetPx >= 0 && /* @__PURE__ */ React.createElement("span", {
		className: "pisell-time-navigator-current-time-bubble",
		style: { left: currentTimeOffsetPx },
		"aria-hidden": true
	}, resolveTimeLabel(wallAligned)), !isHorizontal && currentTimeOffsetPx != null && currentTimeOffsetPx >= 0 && /* @__PURE__ */ React.createElement("div", {
		className: "pisell-time-navigator-current-line",
		style: {
			left: 0,
			top: currentTimeOffsetPx - 1,
			width: "100%",
			height: 2,
			background: "#FF692E"
		},
		"aria-hidden": true
	}), !isAxisMoves && /* @__PURE__ */ React.createElement(CursorLayer, {
		orientation,
		value: visualValue,
		timeFormat,
		formatTimeLabel,
		locale,
		isRealtime: realtime,
		offsetPx: visualCursorOffsetPx,
		style: cursorStyle,
		dragInteractive: !userInteractionLocked,
		isDragging: isCursorDragging,
		onDragPointerDown: handleCursorDragPointerDown,
		onDragPointerMove: handleCursorDragPointerMove,
		onDragPointerUp: handleCursorDragPointerUp,
		onDragPointerCancel: handleCursorDragPointerUp
	})))))), !isAxisMoves && isHorizontal && isCursorDragging && visualValue != null && /* @__PURE__ */ React.createElement("div", {
		className: "pisell-time-navigator-drag-bubble-overlay",
		"aria-hidden": true
	}, /* @__PURE__ */ React.createElement("span", {
		className: classNames("pisell-time-navigator-drag-bubble", realtime && "pisell-time-navigator-drag-bubble--realtime"),
		style: { left: axisStartMarginW + visualCursorOffsetPx - scrollPosition.scrollLeft }
	}, resolveTimeLabel(visualValue))), isAxisMoves && /* @__PURE__ */ React.createElement("div", {
		className: classNames("pisell-time-navigator-cursor-fixed", `pisell-time-navigator-cursor-fixed--${orientation}`),
		"aria-hidden": true
	}, /* @__PURE__ */ React.createElement(CursorLayer, {
		orientation,
		value: effectiveValue,
		timeFormat,
		formatTimeLabel,
		locale,
		isRealtime: realtime,
		offsetPx: 0,
		fixedCenter: true,
		style: cursorStyle
	})), showNowButton && /* @__PURE__ */ React.createElement(NowButton, {
		orientation,
		onClick: handleNow,
		style: nowButtonStyle,
		renderCustom: renderNowButton
	})));
}
PisellTimeNavigator.displayName = "PisellTimeNavigator";
//#endregion
export { PisellTimeNavigator };
