/** @license MIT | @cube-dev/ui-kit v0.147.0 | Cube Dev Team */
import { extractStyles } from "../../../utils/styles.js";
import { useCombinedRefs } from "../../../utils/react/useCombinedRefs.js";
import { useEvent } from "../../../_internal/hooks/use-event.js";
import { BoardGridLinesContext, BoardMetricsContext, useBoardGridLines, useBoardHost, useBoardMetrics, useBoardRegistry } from "./board-context.js";
import { getAllCollisions } from "./grid-core/collision.js";
import { bottom, cloneLayout, correctBounds, getLayoutItem, modifyLayout } from "./grid-core/layout.js";
import { getCompactor } from "./grid-core/compactors.js";
import { calcGridColWidth, calcGridItemPosition, calcWH } from "./grid-core/calculate.js";
import { applySizeConstraints, defaultConstraints } from "./grid-core/constraints.js";
import { BoardProvider } from "./BoardProvider.js";
import { useBoardLayout } from "./use-board-layout.js";
import { WidgetHost } from "./WidgetHost.js";
import { CONTAINER_STYLES, filterBaseProps, mergeStyles, tasty } from "@tenphi/tasty";
import { forwardRef, useEffect, useId, useMemo, useRef, useState, useSyncExternalStore } from "react";
import { jsx, jsxs } from "react/jsx-runtime";
import { useResizeObserver } from "@react-aria/utils";

//#region src/components/layout/Board/Board.tsx
const BoardElement = tasty({
	qa: "Board",
	styles: {
		position: "relative",
		display: "block",
		width: "100%",
		flexGrow: 1,
		minHeight: "0",
		fill: "#surface",
		boxSizing: "border-box"
	}
});
const ContentLayer = tasty({ styles: {
	position: "absolute",
	inset: 0
} });
const PlaceholderElement = tasty({
	qa: "BoardPlaceholder",
	styles: {
		position: "absolute",
		top: 0,
		left: 0,
		fill: "#purple.10",
		radius: "1cr",
		border: "#purple.40",
		zIndex: 2,
		pointerEvents: "none",
		transition: "inset 80ms linear, width 80ms linear, height 80ms linear",
		boxSizing: "border-box"
	}
});
const GridOverlayElement = tasty({
	qa: "BoardGridOverlay",
	styles: {
		position: "absolute",
		zIndex: 0,
		pointerEvents: "none",
		boxSizing: "border-box",
		opacity: ".5"
	}
});
function compactTypeToCore(compact) {
	if (compact === "free") return null;
	return compact;
}
function BoardInner(props, ref) {
	const { id: providedId, layout: controlledLayout, defaultLayout, onLayoutChange, onDragStart, onDrag, onDragStop, onResizeStart, onResize: onResizeProp, onResizeStop, cols = 12, rowHeight = 100, margin = [8, 8], containerPadding, maxRows = Infinity, compact = "vertical", allowOverlap = false, preventCollision = false, isDraggable = true, isResizable = true, isDroppable = true, resizeHandles = ["se"], dragCancel, dragHandle, showGridLines, isAligned = false, constraints, width: providedWidth, widgetProps, children, ...otherProps } = props;
	const registry = useBoardRegistry();
	const parentMetrics = useBoardMetrics();
	const host = useBoardHost();
	const inheritedGridLines = useBoardGridLines();
	const aligned = isAligned && !!parentMetrics;
	const widgetPropsStyles = useMemo(() => {
		if (!widgetProps) return void 0;
		const extracted = extractStyles(widgetProps, CONTAINER_STYLES);
		return Object.keys(extracted).length > 0 ? extracted : void 0;
	}, [widgetProps]);
	const effectiveShowGridLines = showGridLines ?? (inheritedGridLines ? "drag" : false);
	const generatedId = useId();
	const boardId = providedId ?? generatedId;
	const containerRef = useCombinedRefs(ref);
	const contentRef = useRef(null);
	const [measuredWidth, setMeasuredWidth] = useState(0);
	const [measuredHeight, setMeasuredHeight] = useState(0);
	const width = providedWidth ?? measuredWidth;
	const onResizeContainer = useEvent(() => {
		const el = containerRef.current;
		if (!el) return;
		const nextWidth = el.offsetWidth;
		const nextHeight = el.offsetHeight;
		if (nextWidth > 0) setMeasuredWidth(nextWidth);
		if (nextHeight > 0) setMeasuredHeight(nextHeight);
	});
	useResizeObserver({
		ref: containerRef,
		onResize: onResizeContainer
	});
	useEffect(() => {
		if (providedWidth == null) onResizeContainer();
	}, [providedWidth]);
	const { layout, layoutRef, placeholder, placeholderRef, setPlaceholder, applyLayout } = useBoardLayout({
		layout: controlledLayout,
		defaultLayout,
		onLayoutChange
	});
	useSyncExternalStore(registry.store.subscribe, registry.store.getVersion);
	const effectiveMargin = aligned ? parentMetrics.margin : margin;
	const resolvedPadding = containerPadding ?? (aligned ? [0, 0] : effectiveMargin);
	const rows = Math.max(bottom(layout), placeholder ? placeholder.y + placeholder.h : 0);
	const parentColWidth = aligned ? parentMetrics.colWidth : 0;
	const alignedCols = aligned && width > 0 ? Math.max(1, Math.floor((width - resolvedPadding[0] * 2 + effectiveMargin[0] + 2) / (parentColWidth + effectiveMargin[0]))) : cols;
	const effectiveCols = aligned ? alignedCols : cols;
	const effectiveContainerWidth = aligned ? parentColWidth * effectiveCols + effectiveMargin[0] * Math.max(0, effectiveCols - 1) + resolvedPadding[0] * 2 : width;
	const effectiveRowHeight = aligned ? parentMetrics.rowHeight : rowHeight;
	const positionParams = useMemo(() => ({
		margin: effectiveMargin,
		containerPadding: resolvedPadding,
		containerWidth: effectiveContainerWidth,
		cols: effectiveCols,
		rowHeight: effectiveRowHeight,
		maxRows
	}), [
		effectiveMargin[0],
		effectiveMargin[1],
		resolvedPadding[0],
		resolvedPadding[1],
		effectiveContainerWidth,
		effectiveCols,
		effectiveRowHeight,
		maxRows
	]);
	const compactor = useMemo(() => getCompactor(compactTypeToCore(compact), allowOverlap, compact === "free" ? !allowOverlap : preventCollision), [
		compact,
		allowOverlap,
		preventCollision
	]);
	const resolvedConstraints = constraints ?? defaultConstraints;
	const computedHeight = rows > 0 ? rows * effectiveRowHeight + Math.max(0, rows - 1) * effectiveMargin[1] + resolvedPadding[1] * 2 : effectiveRowHeight;
	const containerHeight = aligned && measuredHeight > 0 ? measuredHeight : computedHeight;
	const liveRef = useRef({
		positionParams,
		compactor,
		constraints: resolvedConstraints,
		maxRows,
		containerHeight,
		isDroppable
	});
	liveRef.current = {
		positionParams,
		compactor,
		constraints: resolvedConstraints,
		maxRows,
		containerHeight,
		isDroppable
	};
	const applyLayoutEvent = useEvent(applyLayout);
	const setPlaceholderEvent = useEvent(setPlaceholder);
	const entryRef = useRef(null);
	if (!entryRef.current) entryRef.current = {
		id: boardId,
		getContentRect: () => contentRef.current?.getBoundingClientRect() ?? null,
		getContentNode: () => contentRef.current,
		getPositionParams: () => liveRef.current.positionParams,
		getConstraints: () => liveRef.current.constraints,
		getCompactor: () => liveRef.current.compactor,
		getMaxRows: () => liveRef.current.maxRows,
		getContainerHeight: () => liveRef.current.containerHeight,
		getLayout: () => layoutRef.current,
		applyLayout: (next, commit) => applyLayoutEvent(next, commit),
		setPlaceholder: (item) => setPlaceholderEvent(item),
		isDroppable: () => liveRef.current.isDroppable
	};
	useEffect(() => {
		const entry = entryRef.current;
		entry.id = boardId;
		return registry.registerBoard(entry);
	}, [boardId]);
	const recompactForCols = useEvent((nextCols) => {
		const corrected = correctBounds(cloneLayout(layoutRef.current), { cols: nextCols });
		applyLayoutEvent([...liveRef.current.compactor.compact(corrected, nextCols)], true);
	});
	const prevAlignedColsRef = useRef(null);
	useEffect(() => {
		if (!aligned) {
			prevAlignedColsRef.current = null;
			return;
		}
		if (width <= 0) return;
		if (prevAlignedColsRef.current === null) {
			prevAlignedColsRef.current = effectiveCols;
			return;
		}
		if (prevAlignedColsRef.current === effectiveCols) return;
		prevAlignedColsRef.current = effectiveCols;
		recompactForCols(effectiveCols);
	}, [
		aligned,
		width,
		effectiveCols,
		recompactForCols
	]);
	const naturalHeight = rows > 0 ? rows * effectiveRowHeight + Math.max(0, rows - 1) * effectiveMargin[1] + resolvedPadding[1] * 2 : effectiveRowHeight;
	const requestHeightDeficit = host?.requestHeightDeficit;
	useEffect(() => {
		if (!aligned || !host?.isAutoHeight || !requestHeightDeficit) return;
		if (measuredHeight <= 0) return;
		requestHeightDeficit(naturalHeight - measuredHeight);
	}, [
		aligned,
		host?.isAutoHeight,
		requestHeightDeficit,
		naturalHeight,
		measuredHeight
	]);
	const resizeStateRef = useRef(null);
	const autoHeightMinRowsRef = useRef({});
	const handleResize = useEvent((id, axis, phase, dx, dy) => {
		const pp = liveRef.current.positionParams;
		if (phase === "start") {
			const rawItem = getLayoutItem(layoutRef.current, id);
			if (!rawItem) return;
			const reg = registry.store.get(id);
			const item = {
				...rawItem,
				minW: rawItem.minW ?? reg?.minW ?? widgetProps?.minW,
				maxW: rawItem.maxW ?? reg?.maxW ?? widgetProps?.maxW,
				minH: rawItem.minH ?? reg?.minH ?? widgetProps?.minH,
				maxH: rawItem.maxH ?? reg?.maxH ?? widgetProps?.maxH,
				constraints: rawItem.constraints ?? reg?.constraints ?? widgetProps?.constraints
			};
			resizeStateRef.current = {
				id,
				axis,
				item: { ...item },
				origin: calcGridItemPosition(pp, item.x, item.y, item.w, item.h),
				accX: 0,
				accY: 0
			};
			setPlaceholder({ ...item });
			onResizeStart?.({
				layout: layoutRef.current,
				item: { ...item },
				oldItem: { ...item },
				placeholder: { ...item }
			});
			return;
		}
		const rs = resizeStateRef.current;
		if (!rs) return;
		if (phase === "end") {
			const finalLayout = [...layoutRef.current];
			applyLayout(finalLayout, true);
			setPlaceholder(null);
			onResizeStop?.({
				layout: finalLayout,
				item: getLayoutItem(finalLayout, id) ?? rs.item,
				oldItem: rs.item,
				placeholder: null
			});
			resizeStateRef.current = null;
			return;
		}
		rs.accX += dx;
		rs.accY += dy;
		const { origin, item, axis: handle } = rs;
		let newW = origin.width;
		let newH = origin.height;
		if (handle.includes("e")) newW = origin.width + rs.accX;
		if (handle.includes("w")) newW = origin.width - rs.accX;
		if (handle.includes("s")) newH = origin.height + rs.accY;
		if (handle.includes("n")) newH = origin.height - rs.accY;
		newW = Math.max(newW, 8);
		newH = Math.max(newH, 8);
		const raw = calcWH(pp, newW, newH, item.x, item.y, handle);
		const constrained = applySizeConstraints(liveRef.current.constraints, item, raw.w, raw.h, handle, {
			cols: pp.cols,
			maxRows: liveRef.current.maxRows,
			containerWidth: pp.containerWidth,
			containerHeight: liveRef.current.containerHeight,
			rowHeight: pp.rowHeight,
			margin: pp.margin,
			layout: layoutRef.current
		});
		const floorRows = autoHeightMinRowsRef.current[id] ?? 0;
		const finalW = constrained.w;
		const finalH = Math.max(constrained.h, floorRows);
		let x = item.x;
		let y = item.y;
		if (handle.includes("w")) x = item.x + item.w - finalW;
		if (handle.includes("n")) y = item.y + item.h - finalH;
		x = Math.max(0, x);
		y = Math.max(0, y);
		let newItem = {
			...item,
			x,
			y,
			w: finalW,
			h: finalH
		};
		const compactor = liveRef.current.compactor;
		if (compactor.preventCollision && !compactor.allowOverlap) {
			if (getAllCollisions(layoutRef.current, newItem).some((other) => other.i !== id)) {
				const lastValid = getLayoutItem(layoutRef.current, id);
				if (lastValid) newItem = { ...lastValid };
			}
		}
		const working = modifyLayout(layoutRef.current, newItem);
		const compacted = [...compactor.compact(working, pp.cols)];
		applyLayout(compacted, false);
		const nextPlaceholder = getLayoutItem(compacted, id) ?? newItem;
		setPlaceholder(nextPlaceholder);
		onResizeProp?.({
			layout: compacted,
			item: getLayoutItem(compacted, id) ?? newItem,
			oldItem: rs.item,
			placeholder: nextPlaceholder
		});
	});
	const handleAutoHeight = useEvent((id, neededRows) => {
		autoHeightMinRowsRef.current[id] = neededRows;
		const current = getLayoutItem(layoutRef.current, id);
		if (!current || neededRows <= current.h) return;
		if (registry.dragState || resizeStateRef.current) return;
		const pp = liveRef.current.positionParams;
		const working = modifyLayout(layoutRef.current, {
			...current,
			h: neededRows
		});
		applyLayout([...liveRef.current.compactor.compact(working, pp.cols)], true);
	});
	const dragOldItemRef = useRef(null);
	const handleDragLifecycle = useEvent((id, phase) => {
		const currentLayout = layoutRef.current;
		const liveItem = getLayoutItem(currentLayout, id);
		if (phase === "start") dragOldItemRef.current = liveItem ? { ...liveItem } : null;
		const oldItem = dragOldItemRef.current ?? liveItem;
		if (!oldItem) return;
		const info = {
			layout: currentLayout,
			item: liveItem ?? oldItem,
			oldItem,
			placeholder: placeholderRef.current
		};
		if (phase === "start") onDragStart?.(info);
		else if (phase === "move") onDrag?.(info);
		else {
			onDragStop?.(info);
			dragOldItemRef.current = null;
		}
	});
	const styles = extractStyles(otherProps, CONTAINER_STYLES);
	const dragState = registry.dragState;
	const ready = width > 0;
	const [settled, setSettled] = useState(false);
	useEffect(() => {
		if (!ready) {
			setSettled(false);
			return;
		}
		let id1 = 0;
		let id2 = 0;
		id1 = requestAnimationFrame(() => {
			id2 = requestAnimationFrame(() => setSettled(true));
		});
		return () => {
			cancelAnimationFrame(id1);
			if (id2) cancelAnimationFrame(id2);
		};
	}, [ready]);
	const hostWidgetIsDragging = !!dragState && dragState.nestedBoardIds.has(boardId);
	const placeholderStyle = placeholder ? (() => {
		const pos = calcGridItemPosition(positionParams, placeholder.x, placeholder.y, placeholder.w, placeholder.h);
		return {
			left: `${pos.left}px`,
			top: `${pos.top}px`,
			width: `${pos.width}px`,
			height: `${pos.height}px`
		};
	})() : null;
	const showPlaceholder = placeholder && placeholderStyle;
	const gridOverlayStyle = !hostWidgetIsDragging && (effectiveShowGridLines === true || effectiveShowGridLines === "drag" && (!!dragState || !!placeholder)) ? (() => {
		const colWidth = calcGridColWidth(positionParams);
		const rowHeightPx = positionParams.rowHeight;
		const pitchX = colWidth + effectiveMargin[0];
		const pitchY = rowHeightPx + effectiveMargin[1];
		const padX = resolvedPadding[0];
		const padY = resolvedPadding[1];
		const gridWidth = Math.max(0, effectiveContainerWidth - padX * 2);
		const gridHeight = rows > 0 ? rows * rowHeightPx + (rows - 1) * effectiveMargin[1] : 0;
		const fill = "var(--border-color)";
		const columns = `repeating-linear-gradient(to right, ${fill} 0, ${fill} ${colWidth}px, transparent ${colWidth}px, transparent ${pitchX}px)`;
		const rowsGrad = `repeating-linear-gradient(to bottom, #000 0, #000 ${rowHeightPx}px, transparent ${rowHeightPx}px, transparent ${pitchY}px)`;
		return {
			left: `${padX}px`,
			top: `${padY}px`,
			width: `${gridWidth}px`,
			height: `${gridHeight}px`,
			backgroundImage: columns,
			backgroundPosition: "0 0",
			backgroundRepeat: "repeat",
			maskImage: rowsGrad,
			WebkitMaskImage: rowsGrad,
			maskPosition: "0 0",
			WebkitMaskPosition: "0 0",
			maskRepeat: "repeat",
			WebkitMaskRepeat: "repeat"
		};
	})() : null;
	const boardMetrics = useMemo(() => ({
		colWidth: calcGridColWidth(positionParams),
		rowHeight: positionParams.rowHeight,
		margin: positionParams.margin,
		containerPadding: positionParams.containerPadding
	}), [positionParams]);
	return /* @__PURE__ */ jsx(BoardMetricsContext.Provider, {
		value: boardMetrics,
		children: /* @__PURE__ */ jsx(BoardGridLinesContext.Provider, {
			value: effectiveShowGridLines === true || effectiveShowGridLines === "drag",
			children: /* @__PURE__ */ jsxs(BoardElement, {
				...filterBaseProps(otherProps, { eventProps: true }),
				ref: containerRef,
				styles,
				style: aligned ? void 0 : { minHeight: `${containerHeight}px` },
				mods: {
					dragging: !!dragState,
					"drop-target": dragState?.currentBoardId === boardId
				},
				children: [/* @__PURE__ */ jsxs(ContentLayer, {
					ref: contentRef,
					children: [
						ready && gridOverlayStyle ? /* @__PURE__ */ jsx(GridOverlayElement, {
							"aria-hidden": "true",
							style: gridOverlayStyle
						}) : null,
						ready ? layout.map((item) => {
							const registration = registry.store.get(item.i);
							const widgetDraggable = isDraggable && (registration?.isDraggable ?? widgetProps?.isDraggable) !== false && item.isDraggable !== false && !item.static;
							const widgetResizable = isResizable && (registration?.isResizable ?? widgetProps?.isResizable) !== false && item.isResizable !== false && !item.static;
							const handles = item.resizeHandles ?? registration?.resizeHandles ?? widgetProps?.resizeHandles ?? resizeHandles;
							const widgetDragCancel = registration?.dragCancel ?? widgetProps?.dragCancel ?? dragCancel;
							const widgetDragHandle = registration?.dragHandle ?? widgetProps?.dragHandle ?? dragHandle;
							const widgetIsCard = registration?.isCard ?? widgetProps?.isCard ?? false;
							const widgetIsAutoHeight = registration?.isAutoHeight ?? widgetProps?.isAutoHeight ?? false;
							const widgetQa = registration?.qa ?? widgetProps?.qa;
							return /* @__PURE__ */ jsx(WidgetHost, {
								boardId,
								item,
								positionParams,
								registration,
								isCard: widgetIsCard,
								styles: registration?.styles && widgetPropsStyles ? mergeStyles(widgetPropsStyles, registration.styles) : registration?.styles ?? widgetPropsStyles,
								isDraggable: widgetDraggable,
								isResizable: widgetResizable,
								resizeHandles: handles,
								isAutoHeight: widgetIsAutoHeight,
								qa: widgetQa,
								dragCancel: widgetDragCancel,
								dragHandle: widgetDragHandle,
								registry,
								dragState,
								settled,
								onResize: handleResize,
								onAutoHeight: handleAutoHeight,
								onDragLifecycle: handleDragLifecycle
							}, item.i);
						}) : null,
						showPlaceholder ? /* @__PURE__ */ jsx(PlaceholderElement, {
							"aria-hidden": "true",
							style: placeholderStyle
						}) : null
					]
				}), children]
			})
		})
	});
}
const BoardInnerForwarded = forwardRef(BoardInner);
/**
* A draggable and resizable widget grid. Widgets are declared with
* `Board.Widget` and positioned via a `layout`. Wrap multiple boards in a
* `BoardProvider` to enable dragging widgets between them.
*/
const Board = forwardRef(function Board(props, ref) {
	if (useBoardRegistry()) return /* @__PURE__ */ jsx(BoardInnerForwarded, {
		...props,
		ref
	});
	return /* @__PURE__ */ jsx(BoardProvider, { children: /* @__PURE__ */ jsx(BoardInnerForwarded, {
		...props,
		ref
	}) });
});

//#endregion
export { Board };
//# sourceMappingURL=Board.js.map