import { TWMixin } from "../gui/TWMixin2.js";
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
import { EFTargetable } from "../elements/TargetController.js";
import { panZoomTransformContext } from "../gui/panZoomTransformContext.js";
import { SelectionController } from "./selection/SelectionController.js";
import { selectionContext } from "./selection/selectionContext.js";
import { getElementBounds } from "./getElementBounds.js";
import "./overlays/SelectionOverlay.js";
import { canvasToScreen, screenToCanvas } from "./coordinateTransform.js";
import "../gui/EFOverlayLayer.js";
import { getRotatedBoundingBox, parseRotationFromTransform } from "../gui/transformCalculations.js";
import "../gui/EFTransformHandles.js";
import { findRootTemporal } from "../elements/findRootTemporal.js";
import { consume, provide } from "@lit/context";
import { LitElement, css, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";

//#region src/canvas/EFCanvas.ts
let EFCanvas = class EFCanvas$1 extends EFTargetable(TWMixin(LitElement)) {
	static {
		this.styles = [css`
      :host {
        display: block;
        position: relative;
        width: 100%;
        height: 100%;
      }
      .canvas-content {
        position: relative;
        width: 100%;
        height: 100%;
      }
    `];
	}
	constructor() {
		super();
		this.elementIdAttribute = "data-element-id";
		this.enableTransformHandles = true;
		this.paused = false;
		this.elementRegistry = /* @__PURE__ */ new Map();
		this.elementMetadata = /* @__PURE__ */ new Map();
		this.overlayLayer = null;
		this.selectionOverlay = null;
		this.transformHandlesMap = /* @__PURE__ */ new Map();
		this.overlayRafId = null;
		this.isDragging = false;
		this.dragStarted = false;
		this.dragStartPos = null;
		this.dragStartCanvasPos = null;
		this.dragStartElementPositions = /* @__PURE__ */ new Map();
		this.draggedElementId = null;
		this.capturedPointerId = null;
		this.DRAG_THRESHOLD = 5;
		this.isBoxSelecting = false;
		this.boxSelectStart = null;
		this.boxSelectModifierKeys = false;
		this.emptySpaceClickPos = null;
		this.elementHoverHandlers = /* @__PURE__ */ new Map();
		this._activeRootTemporal = null;
		this.highlightedElement = null;
		this.handlePointerDown = (e) => {
			if (e.button !== 0) return;
			const elementsAtPoint = document.elementsFromPoint(e.clientX, e.clientY);
			let topmostElementId = null;
			for (const el of elementsAtPoint) {
				if (el.tagName === "EF-OVERLAY-LAYER" || el.tagName === "EF-TRANSFORM-HANDLES" || el.closest("ef-overlay-layer") || el.closest("ef-transform-handles")) continue;
				if (!(el instanceof HTMLElement)) continue;
				if (el === this) continue;
				if (!this.contains(el)) continue;
				try {
					this.tryRegisterElement(el);
					const elementId = el.id || el.getAttribute(this.elementIdAttribute);
					if (elementId && this.elementRegistry.has(elementId)) {
						topmostElementId = elementId;
						break;
					}
				} catch {}
				let current = el.parentElement;
				while (current && current !== this) {
					try {
						this.tryRegisterElement(current);
						const elementId = current.id || current.getAttribute(this.elementIdAttribute);
						if (elementId && this.elementRegistry.has(elementId)) {
							topmostElementId = elementId;
							break;
						}
					} catch {}
					current = current.parentElement;
				}
				if (topmostElementId) break;
			}
			if (topmostElementId) {
				const elementId = topmostElementId;
				const isSelected = this.selectionController.getModel().selectedIds.has(elementId);
				if (e.shiftKey) {
					this.selectionController.selectionContext.addToSelection(elementId);
					e.stopPropagation();
				} else if (e.ctrlKey || e.metaKey) {
					this.selectionController.selectionContext.toggle(elementId);
					e.preventDefault();
					e.stopPropagation();
				} else {
					if (!isSelected) this.selectionController.selectionContext.select(elementId);
					const selectedIds = Array.from(this.selectionController.getModel().selectedIds);
					for (const id of selectedIds) {
						this.updateElementMetadata(id);
						const metadata = this.elementMetadata.get(id);
						if (metadata) this.dragStartElementPositions.set(id, {
							x: metadata.x,
							y: metadata.y
						});
					}
					this.isDragging = false;
					this.dragStarted = false;
					this.dragStartPos = {
						x: e.clientX,
						y: e.clientY
					};
					this.draggedElementId = elementId;
					this.capturedPointerId = e.pointerId;
					try {
						this.setPointerCapture(e.pointerId);
					} catch (err) {
						console.warn("[EFCanvas] Failed to capture pointer:", err);
					}
					const canvasRect = (this.shadowRoot?.querySelector(".canvas-content"))?.getBoundingClientRect() || this.getBoundingClientRect();
					this.dragStartCanvasPos = screenToCanvas(e.clientX, e.clientY, canvasRect, this.panZoomTransform);
					e.stopPropagation();
				}
			} else {
				const canvasRect = (this.shadowRoot?.querySelector(".canvas-content"))?.getBoundingClientRect() || this.getBoundingClientRect();
				const canvasPos = screenToCanvas(e.clientX, e.clientY, canvasRect, this.panZoomTransform);
				this.boxSelectModifierKeys = e.shiftKey || e.ctrlKey || e.metaKey;
				this.isBoxSelecting = true;
				this.boxSelectStart = canvasPos;
				this.selectionController.selectionContext.startBoxSelect(canvasPos.x, canvasPos.y);
				this.capturedPointerId = e.pointerId;
				try {
					this.setPointerCapture(e.pointerId);
				} catch (err) {
					console.warn("[EFCanvas] Failed to capture pointer:", err);
				}
				e.preventDefault();
				e.stopPropagation();
			}
		};
		this.handlePointerMove = (e) => {
			if (e.buttons === 0) {
				if (this.capturedPointerId !== null) {
					try {
						this.releasePointerCapture(e.pointerId);
					} catch (err) {}
					this.capturedPointerId = null;
				}
				if (this.isDragging || this.dragStarted || this.draggedElementId !== null) {
					this.isDragging = false;
					this.dragStarted = false;
					this.dragStartPos = null;
					this.dragStartCanvasPos = null;
					this.dragStartElementPositions.clear();
					this.draggedElementId = null;
				}
				if (this.isBoxSelecting) {
					this.isBoxSelecting = false;
					this.boxSelectStart = null;
					this.boxSelectModifierKeys = false;
				}
				return;
			}
			const canvasRect = (this.shadowRoot?.querySelector(".canvas-content"))?.getBoundingClientRect() || this.getBoundingClientRect();
			if (!this.dragStarted && this.draggedElementId && this.dragStartPos && this.dragStartCanvasPos && this.dragStartElementPositions.size > 0) if (Math.sqrt(Math.pow(e.clientX - this.dragStartPos.x, 2) + Math.pow(e.clientY - this.dragStartPos.y, 2)) >= this.DRAG_THRESHOLD) {
				this.dragStarted = true;
				this.isDragging = true;
			} else return;
			if (this.isDragging && this.dragStarted && this.dragStartCanvasPos && this.dragStartElementPositions.size > 0) {
				const canvasPos = screenToCanvas(e.clientX, e.clientY, canvasRect, this.panZoomTransform);
				const deltaX = canvasPos.x - this.dragStartCanvasPos.x;
				const deltaY = canvasPos.y - this.dragStartCanvasPos.y;
				for (const [elementId, startPos] of this.dragStartElementPositions.entries()) {
					const newX = startPos.x + deltaX;
					const newY = startPos.y + deltaY;
					this.updateElementPosition(elementId, newX, newY);
				}
				e.stopPropagation();
			} else if (this.isBoxSelecting && this.boxSelectStart) {
				const canvasPos = screenToCanvas(e.clientX, e.clientY, canvasRect, this.panZoomTransform);
				this.selectionController.selectionContext.updateBoxSelect(canvasPos.x, canvasPos.y);
				e.stopPropagation();
			}
		};
		this.handlePointerUp = (e) => {
			const wasDragging = this.isDragging || this.dragStarted;
			const wasBoxSelecting = this.isBoxSelecting;
			if (this.capturedPointerId !== null) {
				try {
					this.releasePointerCapture(e.pointerId);
				} catch (err) {}
				this.capturedPointerId = null;
			}
			if (this.draggedElementId !== null || this.dragStartPos !== null) {
				this.isDragging = false;
				this.dragStarted = false;
				this.dragStartPos = null;
				this.dragStartCanvasPos = null;
				this.dragStartElementPositions.clear();
				this.draggedElementId = null;
			}
			if (this.isBoxSelecting) {
				this.isBoxSelecting = false;
				this.selectionController.selectionContext.endBoxSelect((bounds) => this.hitTest(bounds), this.boxSelectModifierKeys);
				this.boxSelectStart = null;
				this.boxSelectModifierKeys = false;
			}
			if (this.emptySpaceClickPos) {
				if (!wasDragging && !wasBoxSelecting) {
					if (!(Math.abs(e.clientX - this.emptySpaceClickPos.x) > 2 || Math.abs(e.clientY - this.emptySpaceClickPos.y) > 2)) this.selectionController.selectionContext.clear();
				}
				this.emptySpaceClickPos = null;
			}
		};
		this.wasPaused = false;
		this.lastMultiSelectionRotation = null;
		this.multiSelectionRotationCenter = null;
		this.multiSelectionResizeInitial = null;
		this.selectionController = new SelectionController(this);
		this.selectionController.setHitTest((bounds) => this.hitTest(bounds));
		this.selectionContext = this.selectionController.selectionContext;
	}
	connectedCallback() {
		super.connectedCallback();
		this.setupElementTracking();
		this.setupOverlayLayer();
		this.setupSelectionOverlay();
		this.setupSelectionListener();
		this.addEventListener("pointerdown", this.handlePointerDown);
		this.addEventListener("pointermove", this.handlePointerMove);
		this.addEventListener("pointerup", this.handlePointerUp);
		this.addEventListener("pointercancel", this.handlePointerUp);
		this.startOverlayRafLoop();
	}
	disconnectedCallback() {
		super.disconnectedCallback();
		this.stopOverlayRafLoop();
		this.cleanupOverlayLayer();
		this.cleanupSelectionOverlay();
		this.cleanupTransformHandles();
		this.removeSelectionListener();
		this.removeEventListener("pointerdown", this.handlePointerDown);
		this.removeEventListener("pointermove", this.handlePointerMove);
		this.removeEventListener("pointerup", this.handlePointerUp);
		this.removeEventListener("pointercancel", this.handlePointerUp);
	}
	/**
	* Setup mutation observer to track element additions/removals.
	*/
	setupElementTracking() {
		new MutationObserver((mutations) => {
			for (const mutation of mutations) {
				for (const node of Array.from(mutation.addedNodes)) if (node instanceof HTMLElement) this.tryRegisterElement(node);
				for (const node of Array.from(mutation.removedNodes)) if (node instanceof HTMLElement) this.unregisterElement(node);
			}
		}).observe(this, {
			childList: true,
			subtree: true
		});
		const registerAllElements = (parent) => {
			for (const child of Array.from(parent.children)) if (child instanceof HTMLElement) {
				this.tryRegisterElement(child);
				if (child.children.length > 0) registerAllElements(child);
			}
		};
		registerAllElements(this);
	}
	/**
	* Try to register an element, auto-generating an ID if needed.
	* Public method for external use (e.g., hierarchy selection).
	*/
	tryRegisterElement(element) {
		const existingId = element.getAttribute(this.elementIdAttribute) || element.id;
		if (existingId && this.elementRegistry.has(existingId)) return;
		try {
			let elementId = element.id && element.id.trim() !== "" ? element.id : element.getAttribute(this.elementIdAttribute);
			if (!elementId) elementId = `${element.tagName.toLowerCase()}-${Array.from(element.parentElement?.children || []).indexOf(element)}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
			if (!element.id || element.id.trim() === "") element.id = elementId;
			if (!element.getAttribute(this.elementIdAttribute)) element.setAttribute(this.elementIdAttribute, elementId);
			this.registerElement(element, elementId);
		} catch (error) {}
	}
	/**
	* Register an element for canvas management.
	* @throws Error if element does not have an ID
	*/
	registerElement(element, id) {
		const elementId = id || element.getAttribute(this.elementIdAttribute) || (element.id && element.id.trim() !== "" ? element.id : null);
		if (!elementId) throw new Error(`Element must have an ID. Provide either the 'id' parameter, set the '${this.elementIdAttribute}' attribute, or set the 'id' attribute on the element.`);
		if (this.elementRegistry.has(elementId)) {
			if (this.elementRegistry.get(elementId) !== element) throw new Error(`Element with ID '${elementId}' is already registered. Each element must have a unique ID.`);
			return elementId;
		}
		if (!element.getAttribute(this.elementIdAttribute)) element.setAttribute(this.elementIdAttribute, elementId);
		if (!element.id || element.id.trim() === "") element.id = elementId;
		this.elementRegistry.set(elementId, element);
		this.setupElementHoverListeners(element, elementId);
		if (element.parentElement === this) {
			if (!element.style.position) element.style.position = "absolute";
			if (!element.style.display || element.style.display === "none") element.style.display = "block";
		}
		this.updateElementMetadata(elementId);
		return elementId;
	}
	/**
	* Unregister an element.
	*/
	unregisterElement(element) {
		const elementId = typeof element === "string" ? element : element.getAttribute(this.elementIdAttribute);
		if (elementId) {
			const elementToUnregister = typeof element === "string" ? this.elementRegistry.get(elementId) : element;
			if (elementToUnregister) {
				elementToUnregister.removeAttribute("data-selected");
				elementToUnregister.removeAttribute("data-highlighted");
			}
			this.removeElementHoverListeners(elementId);
			this.elementRegistry.delete(elementId);
			this.elementMetadata.delete(elementId);
			this.selectionController.selectionContext.deselect(elementId);
		}
	}
	/**
	* Setup hover event listeners for an element to update highlightedElement.
	* The canvas is the source of truth for highlight state.
	*/
	setupElementHoverListeners(element, elementId) {
		this.removeElementHoverListeners(elementId);
		const mouseenterHandler = () => {
			this.setHighlightedElement(element);
		};
		const mouseleaveHandler = () => {
			if (this.highlightedElement === element) this.setHighlightedElement(null);
		};
		element.addEventListener("mouseenter", mouseenterHandler);
		element.addEventListener("mouseleave", mouseleaveHandler);
		this.elementHoverHandlers.set(elementId, {
			mouseenter: mouseenterHandler,
			mouseleave: mouseleaveHandler
		});
	}
	/**
	* Set the highlighted element. Called by canvas hover handlers
	* and by external panels (hierarchy, timeline) when user hovers items.
	*/
	setHighlightedElement(element) {
		if (this.highlightedElement !== element) {
			if (this.highlightedElement) this.highlightedElement.removeAttribute("data-highlighted");
			this.highlightedElement = element;
			if (element) element.setAttribute("data-highlighted", "true");
		}
	}
	/**
	* Remove hover event listeners for an element.
	*/
	removeElementHoverListeners(elementId) {
		const handlers = this.elementHoverHandlers.get(elementId);
		if (!handlers) return;
		const element = this.elementRegistry.get(elementId);
		if (element) {
			element.removeEventListener("mouseenter", handlers.mouseenter);
			element.removeEventListener("mouseleave", handlers.mouseleave);
		}
		this.elementHoverHandlers.delete(elementId);
	}
	/**
	* Update element metadata from DOM.
	*
	* UNIFIED APPROACH - works for ALL elements regardless of rotation, scale, or nesting:
	*
	* 1. DIMENSIONS: Always use offsetWidth/offsetHeight
	*    - These are layout dimensions in the element's coordinate space
	*    - Unaffected by CSS transforms (rotation, scale, etc.)
	*    - Already in canvas coordinates (no scale division needed)
	*
	* 2. CENTER POSITION: Always use getBoundingClientRect() center
	*    - The center of the bounding box IS the element's center (transform-origin: center)
	*    - Works correctly for rotated elements (center is rotation-invariant)
	*    - Convert to canvas coordinates using screenToCanvas()
	*
	* 3. TOP-LEFT POSITION: Calculate from center and dimensions
	*    - x = centerX - width/2
	*    - y = centerY - height/2
	*/
	updateElementMetadata(elementId) {
		const element = this.elementRegistry.get(elementId);
		if (!element) return;
		const canvasContent = this.shadowRoot?.querySelector(".canvas-content");
		let actualWidth = element.offsetWidth;
		let actualHeight = element.offsetHeight;
		if (actualWidth === 0 || actualHeight === 0) {
			const elementRect$1 = getElementBounds(element);
			const scale = this.panZoomTransform?.scale || 1;
			actualWidth = elementRect$1.width / scale;
			actualHeight = elementRect$1.height / scale;
		}
		const elementRect = getElementBounds(element);
		const screenCenterX = elementRect.left + elementRect.width / 2;
		const screenCenterY = elementRect.top + elementRect.height / 2;
		let canvasX;
		let canvasY;
		if (!canvasContent) {
			const existingMetadata = this.elementMetadata.get(elementId);
			canvasX = existingMetadata?.x ?? 0;
			canvasY = existingMetadata?.y ?? 0;
		} else {
			const canvasCenter = screenToCanvas(screenCenterX, screenCenterY, canvasContent.getBoundingClientRect(), this.panZoomTransform);
			canvasX = canvasCenter.x - actualWidth / 2;
			canvasY = canvasCenter.y - actualHeight / 2;
		}
		let rotation = this.elementMetadata.get(elementId)?.rotation;
		if (rotation === void 0) {
			rotation = parseRotationFromTransform(window.getComputedStyle(element).transform);
			if (rotation === 0) rotation = void 0;
		}
		this.elementMetadata.set(elementId, {
			id: elementId,
			element,
			x: canvasX,
			y: canvasY,
			width: actualWidth,
			height: actualHeight,
			rotation
		});
	}
	/**
	* Hit test - find elements intersecting with bounds.
	* @param bounds - DOMRect in canvas coordinate space (from SelectionModel.boxSelectBounds)
	*/
	hitTest(bounds) {
		const testRect = bounds;
		const canvasRect = this.getBoundingClientRect();
		const hitIds = [];
		for (const [elementId, element] of this.elementRegistry.entries()) {
			const elementBounds = getElementBounds(element);
			const elementCanvasPos = screenToCanvas(elementBounds.left, elementBounds.top, canvasRect, this.panZoomTransform);
			const elementCanvasWidth = elementBounds.width / (this.panZoomTransform?.scale || 1);
			const elementCanvasHeight = elementBounds.height / (this.panZoomTransform?.scale || 1);
			const elementRect = new DOMRect(elementCanvasPos.x, elementCanvasPos.y, elementCanvasWidth, elementCanvasHeight);
			if (testRect.left < elementRect.right && testRect.right > elementRect.left && testRect.top < elementRect.bottom && testRect.bottom > elementRect.top) hitIds.push(elementId);
		}
		return hitIds;
	}
	/**
	* Setup listener for selection changes to update data-selected attributes.
	*/
	setupSelectionListener() {
		if (this.selectionChangeHandler) return;
		this.selectionChangeHandler = () => {
			this.updateSelectionAttributes();
		};
		this.selectionContext.addEventListener("selectionchange", this.selectionChangeHandler);
		this.updateSelectionAttributes();
	}
	/**
	* Remove selection change listener.
	*/
	removeSelectionListener() {
		if (this.selectionChangeHandler) {
			this.selectionContext.removeEventListener("selectionchange", this.selectionChangeHandler);
			this.selectionChangeHandler = void 0;
		}
	}
	/**
	* Get the root temporal element containing the current selection.
	* Returns null if no element is selected or the selected element has no root temporal.
	*/
	get activeRootTemporal() {
		return this._activeRootTemporal;
	}
	/**
	* Update data-selected attributes on elements based on current selection.
	* This enables pure CSS styling of selected elements.
	*/
	updateSelectionAttributes() {
		const selectedIds = Array.from(this.selectionContext.selectedIds);
		const allRegisteredIds = Array.from(this.elementRegistry.keys());
		for (const id of allRegisteredIds) {
			const element = this.elementRegistry.get(id);
			if (element) element.removeAttribute("data-selected");
		}
		for (const id of selectedIds) {
			const element = this.elementRegistry.get(id);
			if (element) element.setAttribute("data-selected", "true");
		}
		const previousActiveRootTemporal = this._activeRootTemporal;
		let newActiveRootTemporal = null;
		if (selectedIds.length > 0) {
			const selectedElement = document.getElementById(selectedIds[0] || "");
			if (selectedElement) newActiveRootTemporal = findRootTemporal(selectedElement);
		}
		this._activeRootTemporal = newActiveRootTemporal;
		if (previousActiveRootTemporal !== newActiveRootTemporal) this.dispatchEvent(new CustomEvent("activeroottemporalchange", {
			detail: { activeRootTemporal: newActiveRootTemporal },
			bubbles: true,
			composed: true
		}));
	}
	/**
	* Update element position in canvas coordinates.
	* Unified approach: Always calculate relative to parent (or .canvas-content for direct children).
	* For direct children, parent position is (0, 0), so relative = absolute (no-op).
	*
	* For nested elements, we read parent's current position from DOM (not metadata) to ensure
	* we're always calculating relative to the actual current position.
	*/
	updateElementPosition(elementId, x, y) {
		const element = this.elementRegistry.get(elementId);
		if (!element) {
			console.warn("[EFCanvas] updateElementPosition: element not found", elementId);
			return;
		}
		const metadata = this.elementMetadata.get(elementId);
		if (!metadata) return;
		metadata.x = x;
		metadata.y = y;
		let parentX = 0;
		let parentY = 0;
		let parent = element.parentElement;
		while (parent && parent !== this) {
			const parentId = parent.id || parent.getAttribute(this.elementIdAttribute);
			if (parentId && this.elementRegistry.has(parentId)) {
				const parentWidth = parent.offsetWidth;
				const parentHeight = parent.offsetHeight;
				const parentRect = getElementBounds(parent);
				const parentScreenCenterX = parentRect.left + parentRect.width / 2;
				const parentScreenCenterY = parentRect.top + parentRect.height / 2;
				const canvasContent = this.shadowRoot?.querySelector(".canvas-content");
				if (canvasContent) {
					const parentCanvasCenter = screenToCanvas(parentScreenCenterX, parentScreenCenterY, canvasContent.getBoundingClientRect(), this.panZoomTransform);
					parentX = parentCanvasCenter.x - parentWidth / 2;
					parentY = parentCanvasCenter.y - parentHeight / 2;
				}
				break;
			}
			parent = parent.parentElement;
		}
		const relativeX = x - parentX;
		const relativeY = y - parentY;
		element.style.position = "absolute";
		element.style.left = `${relativeX}px`;
		element.style.top = `${relativeY}px`;
	}
	/**
	* Get element metadata.
	*/
	getElementData(elementId) {
		return this.elementMetadata.get(elementId) || null;
	}
	/**
	* Get all element data.
	*/
	getAllElementsData() {
		return Array.from(this.elementMetadata.values());
	}
	/**
	* Convert screen coordinates to canvas coordinates (for API).
	*/
	screenToCanvasCoords(screenX, screenY) {
		return screenToCanvas(screenX, screenY, this.getBoundingClientRect(), this.panZoomTransform);
	}
	/**
	* Convert canvas coordinates to screen coordinates (for API).
	*/
	canvasToScreenCoords(canvasX, canvasY) {
		return canvasToScreen(canvasX, canvasY, this.getBoundingClientRect(), this.panZoomTransform);
	}
	/**
	* Setup overlay layer as sibling of panzoom (outside panzoom, same level as panzoom).
	*/
	setupOverlayLayer() {
		const panZoom = this.closest("ef-pan-zoom");
		if (!panZoom) return;
		const panZoomParent = panZoom.parentElement;
		if (panZoomParent) {
			const existing = panZoomParent.querySelector("ef-overlay-layer");
			if (existing) {
				this.overlayLayer = existing;
				return;
			}
		}
		if (panZoomParent) {
			const overlayLayer = document.createElement("ef-overlay-layer");
			overlayLayer.style.position = "absolute";
			overlayLayer.style.inset = "0";
			overlayLayer.style.zIndex = "1";
			overlayLayer.style.pointerEvents = "none";
			panZoomParent.insertBefore(overlayLayer, panZoom.nextSibling);
			this.overlayLayer = overlayLayer;
		}
	}
	/**
	* Cleanup overlay layer if we created it.
	*/
	cleanupOverlayLayer() {
		if (this.overlayLayer && this.overlayLayer.parentElement) {
			const parent = this.parentElement;
			if (parent && parent.contains(this.overlayLayer)) {
				if (this.overlayLayer.tagName !== "EF-CANVAS") this.overlayLayer.remove();
			}
		}
		this.overlayLayer = null;
	}
	/**
	* Setup selection overlay as sibling of panzoom (outside panzoom, same level as panzoom).
	* This ensures the overlay maintains 1:1 pixel ratio regardless of zoom level.
	*/
	setupSelectionOverlay() {
		const panZoom = this.closest("ef-pan-zoom");
		if (!panZoom) return;
		const panZoomParent = panZoom.parentElement;
		if (panZoomParent) {
			const existing = panZoomParent.querySelector("ef-canvas-selection-overlay");
			if (existing) {
				this.selectionOverlay = existing;
				return;
			}
		}
		if (panZoomParent) {
			const selectionOverlay = document.createElement("ef-canvas-selection-overlay");
			selectionOverlay.selection = this.selectionContext;
			selectionOverlay.canvas = this;
			if (this.panZoomTransform) selectionOverlay.panZoomTransform = this.panZoomTransform;
			panZoomParent.insertBefore(selectionOverlay, panZoom.nextSibling);
			this.selectionOverlay = selectionOverlay;
		}
	}
	/**
	* Cleanup selection overlay if we created it.
	*/
	cleanupSelectionOverlay() {
		if (this.selectionOverlay && this.selectionOverlay.parentElement) {
			const panZoom = this.closest("ef-pan-zoom");
			if (panZoom && panZoom.parentElement?.contains(this.selectionOverlay)) this.selectionOverlay.remove();
			this.selectionOverlay = null;
		}
	}
	startOverlayRafLoop() {
		if (this.overlayRafId !== null) return;
		const update = () => {
			if (this.paused) {
				this.wasPaused = true;
				this.overlayRafId = requestAnimationFrame(update);
				return;
			}
			if (this.wasPaused) {
				this.wasPaused = false;
				const selectedIds = Array.from(this.selectionController.getModel().selectedIds);
				for (const id of selectedIds) this.updateElementMetadata(id);
			}
			if (this.overlayLayer && this.panZoomTransform) this.overlayLayer.panZoomTransform = this.panZoomTransform;
			if (this.enableTransformHandles) this.updateTransformHandles();
			this.overlayRafId = requestAnimationFrame(update);
		};
		this.overlayRafId = requestAnimationFrame(update);
	}
	/**
	* Stop RAF loop.
	*/
	stopOverlayRafLoop() {
		if (this.overlayRafId !== null) {
			cancelAnimationFrame(this.overlayRafId);
			this.overlayRafId = null;
		}
	}
	/**
	* Update transform handles for selected elements.
	* For multiple selections, shows a single set of handles for the bounding box.
	*/
	updateTransformHandles() {
		if (!this.overlayLayer) return;
		const selectedIds = Array.from(this.selectionController.getModel().selectedIds);
		this.transformHandlesMap.forEach((handles$1, id) => {
			const isMultiSelectionHandle = id === "multi-selection";
			const isMultiSelection = selectedIds.length > 1;
			let shouldKeep;
			if (isMultiSelectionHandle) shouldKeep = isMultiSelection;
			else shouldKeep = selectedIds.includes(id) && !isMultiSelection;
			if (!shouldKeep) {
				handles$1.remove();
				this.transformHandlesMap.delete(id);
			}
		});
		if (selectedIds.length === 0) return;
		const handleKey = selectedIds.length > 1 ? "multi-selection" : selectedIds[0] ?? "none";
		if (handleKey === "none") return;
		let handles = this.transformHandlesMap.get(handleKey);
		if (!handles) {
			handles = document.createElement("ef-transform-handles");
			handles.setAttribute("enable-rotation", "true");
			handles.setAttribute("enable-resize", "true");
			handles.setAttribute("enable-drag", "false");
			if (selectedIds.length > 1) handles.setAttribute("lock-aspect-ratio", "true");
			handles.style.pointerEvents = "none";
			handles.addEventListener("bounds-change", (e) => {
				const bounds = e.detail.bounds;
				const currentSelectedIds = Array.from(this.selectionController.getModel().selectedIds);
				if (currentSelectedIds.length > 1) this.handleMultiSelectionTransformHandlesBoundsChange(currentSelectedIds, bounds);
				else if (currentSelectedIds[0]) this.handleTransformHandlesBoundsChange(currentSelectedIds[0], bounds);
			});
			handles.addEventListener("rotation-change", (e) => {
				const rotation = e.detail.rotation;
				const currentSelectedIds = Array.from(this.selectionController.getModel().selectedIds);
				if (currentSelectedIds.length > 1) this.handleMultiSelectionTransformHandlesRotationChange(currentSelectedIds, rotation);
				else if (currentSelectedIds[0]) this.handleTransformHandlesRotationChange(currentSelectedIds[0], rotation);
			});
			this.overlayLayer.appendChild(handles);
			this.transformHandlesMap.set(handleKey, handles);
		}
		if (selectedIds.length > 1) {
			let minX = Infinity;
			let minY = Infinity;
			let maxX = -Infinity;
			let maxY = -Infinity;
			let hasElements = false;
			for (const id of selectedIds) {
				this.updateElementMetadata(id);
				const metadata = this.elementMetadata.get(id);
				if (metadata) {
					const rotatedBounds = getRotatedBoundingBox(metadata.x, metadata.y, metadata.width, metadata.height, metadata.rotation ?? 0);
					minX = Math.min(minX, rotatedBounds.minX);
					minY = Math.min(minY, rotatedBounds.minY);
					maxX = Math.max(maxX, rotatedBounds.maxX);
					maxY = Math.max(maxY, rotatedBounds.maxY);
					hasElements = true;
				}
			}
			if (hasElements && this.overlayLayer) {
				const firstElementId = selectedIds[0];
				if (firstElementId) {
					const firstElement = this.elementRegistry.get(firstElementId);
					if (firstElement) handles.target = firstElement;
				}
				const panZoomElement = this.closest("ef-pan-zoom");
				const overlayRect = this.overlayLayer.getBoundingClientRect();
				let screenX;
				let screenY;
				let screenWidth;
				let screenHeight;
				if (panZoomElement && typeof panZoomElement.canvasToScreen === "function" && this.panZoomTransform) {
					const topLeft = panZoomElement.canvasToScreen(minX, minY);
					const bottomRight = panZoomElement.canvasToScreen(maxX, maxY);
					screenX = topLeft.x;
					screenY = topLeft.y;
					screenWidth = bottomRight.x - topLeft.x;
					screenHeight = bottomRight.y - topLeft.y;
				} else {
					const canvasRect = this.getBoundingClientRect();
					const topLeft = canvasToScreen(minX, minY, canvasRect, this.panZoomTransform);
					const bottomRight = canvasToScreen(maxX, maxY, canvasRect, this.panZoomTransform);
					screenX = topLeft.x;
					screenY = topLeft.y;
					screenWidth = bottomRight.x - topLeft.x;
					screenHeight = bottomRight.y - topLeft.y;
				}
				if (handles.interactionMode === "rotating" || handles.interactionMode === "resizing") {
					const newScale = this.panZoomTransform?.scale || 1;
					if (handles.canvasScale !== newScale) handles.canvasScale = newScale;
				} else {
					const currentBounds = handles.bounds;
					const newBounds = {
						x: screenX - overlayRect.left,
						y: screenY - overlayRect.top,
						width: screenWidth,
						height: screenHeight,
						rotation: 0
					};
					if (!currentBounds || Math.abs(currentBounds.x - newBounds.x) > .1 || Math.abs(currentBounds.y - newBounds.y) > .1 || Math.abs(currentBounds.width - newBounds.width) > .1 || Math.abs(currentBounds.height - newBounds.height) > .1 || Math.abs((currentBounds.rotation ?? 0) - (newBounds.rotation ?? 0)) > .1) handles.bounds = newBounds;
					const newScale = this.panZoomTransform?.scale || 1;
					if (handles.canvasScale !== newScale) handles.canvasScale = newScale;
					if (handles.interactionMode === "idle") {
						this.lastMultiSelectionRotation = null;
						this.multiSelectionRotationCenter = null;
						this.multiSelectionResizeInitial = null;
						handles.enableResize = true;
					}
				}
			}
		} else if (selectedIds[0]) {
			this.updateElementMetadata(selectedIds[0]);
			const elementData = this.elementMetadata.get(selectedIds[0]);
			if (elementData && elementData.element) {
				handles.target = elementData.element;
				this.updateTransformHandlesBounds(selectedIds[0], handles, elementData);
			}
		}
	}
	/**
	* Update transform handles bounds for an element.
	*/
	updateTransformHandlesBounds(_elementId, handles, elementData) {
		if (!this.overlayLayer) return;
		const overlayRect = this.overlayLayer.getBoundingClientRect();
		const canvasContent = this.shadowRoot?.querySelector(".canvas-content");
		if (!canvasContent) return;
		const canvasRect = canvasContent.getBoundingClientRect();
		const scale = this.panZoomTransform?.scale || 1;
		const centerScreen = canvasToScreen(elementData.x + elementData.width / 2, elementData.y + elementData.height / 2, canvasRect, this.panZoomTransform);
		const screenWidth = elementData.width * scale;
		const screenHeight = elementData.height * scale;
		const newBounds = {
			x: centerScreen.x - overlayRect.left - screenWidth / 2,
			y: centerScreen.y - overlayRect.top - screenHeight / 2,
			width: screenWidth,
			height: screenHeight,
			rotation: elementData.rotation || 0
		};
		const currentBounds = handles.bounds;
		if (!currentBounds || Math.abs(currentBounds.x - newBounds.x) > .1 || Math.abs(currentBounds.y - newBounds.y) > .1 || Math.abs(currentBounds.width - newBounds.width) > .1 || Math.abs(currentBounds.height - newBounds.height) > .1 || (currentBounds.rotation || 0) !== (newBounds.rotation || 0)) handles.bounds = newBounds;
		if (handles.canvasScale !== scale) handles.canvasScale = scale;
	}
	/**
	* Handle transform handles bounds-change event for multi-selection.
	* Updates all selected elements proportionally.
	*/
	handleMultiSelectionTransformHandlesBoundsChange(elementIds, bounds) {
		if (!this.overlayLayer) return;
		const newCanvasPos = {
			x: bounds.x,
			y: bounds.y
		};
		const newCanvasWidth = bounds.width;
		const newCanvasHeight = bounds.height;
		if (!this.multiSelectionResizeInitial) {
			let minX$1 = Infinity;
			let minY$1 = Infinity;
			let maxX$1 = -Infinity;
			let maxY$1 = -Infinity;
			const elements = /* @__PURE__ */ new Map();
			for (const id of elementIds) {
				const metadata = this.elementMetadata.get(id);
				if (metadata) {
					elements.set(id, {
						x: metadata.x,
						y: metadata.y,
						width: metadata.width,
						height: metadata.height,
						rotation: metadata.rotation ?? 0
					});
					const rotatedBounds = getRotatedBoundingBox(metadata.x, metadata.y, metadata.width, metadata.height, metadata.rotation ?? 0);
					minX$1 = Math.min(minX$1, rotatedBounds.minX);
					minY$1 = Math.min(minY$1, rotatedBounds.minY);
					maxX$1 = Math.max(maxX$1, rotatedBounds.maxX);
					maxY$1 = Math.max(maxY$1, rotatedBounds.maxY);
				}
			}
			this.multiSelectionResizeInitial = {
				elements,
				minX: minX$1,
				minY: minY$1,
				maxX: maxX$1,
				maxY: maxY$1
			};
		}
		const { elements: initialElements, minX, minY, maxX, maxY } = this.multiSelectionResizeInitial;
		const oldWidth = maxX - minX;
		const oldHeight = maxY - minY;
		if (oldWidth === 0 || oldHeight === 0) return;
		const scaleX = newCanvasWidth / oldWidth;
		const scaleY = newCanvasHeight / oldHeight;
		const safeScaleX = Math.max(.01, scaleX);
		const safeScaleY = Math.max(.01, scaleY);
		for (const [id, initialData] of initialElements.entries()) {
			const relX = (initialData.x - minX) / oldWidth;
			const relY = (initialData.y - minY) / oldHeight;
			const newX = newCanvasPos.x + relX * newCanvasWidth;
			const newY = newCanvasPos.y + relY * newCanvasHeight;
			const newWidth = initialData.width * safeScaleX;
			const newHeight = initialData.height * safeScaleY;
			this.updateElementPosition(id, newX, newY);
			const element = this.elementRegistry.get(id);
			if (!element) {
				console.warn("[EFCanvas] handleMultiSelectionTransformHandlesBoundsChange: element not found", id);
				continue;
			}
			element.style.width = `${newWidth}px`;
			element.style.height = `${newHeight}px`;
			const metadata = this.elementMetadata.get(id);
			if (metadata) this.elementMetadata.set(id, {
				...metadata,
				x: newX,
				y: newY,
				width: newWidth,
				height: newHeight
			});
		}
		const handles = this.transformHandlesMap.get("multi-selection");
		if (handles && this.overlayLayer) {
			const overlayRect = this.overlayLayer.getBoundingClientRect();
			const canvasRect = this.getBoundingClientRect();
			const scale = this.panZoomTransform?.scale || 1;
			const centerScreen = canvasToScreen(newCanvasPos.x + newCanvasWidth / 2, newCanvasPos.y + newCanvasHeight / 2, canvasRect, this.panZoomTransform);
			const screenWidth = newCanvasWidth * scale;
			const screenHeight = newCanvasHeight * scale;
			handles.bounds = {
				x: centerScreen.x - overlayRect.left - screenWidth / 2,
				y: centerScreen.y - overlayRect.top - screenHeight / 2,
				width: screenWidth,
				height: screenHeight,
				rotation: 0
			};
		}
	}
	/**
	* Handle transform handles rotation-change event for multiple selected elements.
	* Rotates all elements around the center of the bounding box.
	*/
	handleMultiSelectionTransformHandlesRotationChange(elementIds, rotation) {
		const isFirstCall = this.lastMultiSelectionRotation === null;
		if (isFirstCall) {
			let minX = Infinity;
			let minY = Infinity;
			let maxX = -Infinity;
			let maxY = -Infinity;
			for (const id of elementIds) {
				const metadata = this.elementMetadata.get(id);
				if (metadata) {
					minX = Math.min(minX, metadata.x);
					minY = Math.min(minY, metadata.y);
					maxX = Math.max(maxX, metadata.x + metadata.width);
					maxY = Math.max(maxY, metadata.y + metadata.height);
				}
			}
			this.multiSelectionRotationCenter = {
				x: (minX + maxX) / 2,
				y: (minY + maxY) / 2
			};
		}
		const deltaRotation = isFirstCall ? 0 : rotation - this.lastMultiSelectionRotation;
		this.lastMultiSelectionRotation = rotation;
		const groupCenterX = this.multiSelectionRotationCenter.x;
		const groupCenterY = this.multiSelectionRotationCenter.y;
		const deltaRadians = deltaRotation * Math.PI / 180;
		const cos = Math.cos(deltaRadians);
		const sin = Math.sin(deltaRadians);
		for (const id of elementIds) {
			const metadata = this.elementMetadata.get(id);
			const element = this.elementRegistry.get(id);
			if (!metadata || !element) continue;
			const data = {
				id,
				element,
				x: metadata.x,
				y: metadata.y,
				width: metadata.width,
				height: metadata.height,
				rotation: metadata.rotation ?? 0
			};
			if (Math.abs(deltaRotation) < .001) continue;
			const elementCenterX = data.x + data.width / 2;
			const elementCenterY = data.y + data.height / 2;
			const relX = elementCenterX - groupCenterX;
			const relY = elementCenterY - groupCenterY;
			const rotatedRelX = relX * cos - relY * sin;
			const rotatedRelY = relX * sin + relY * cos;
			const newX = groupCenterX + rotatedRelX - data.width / 2;
			const newY = groupCenterY + rotatedRelY - data.height / 2;
			this.updateElementPosition(data.id, newX, newY);
			const newRotation = data.rotation + deltaRotation;
			data.element.style.transform = `rotate(${newRotation}deg)`;
			data.element.style.transformOrigin = "center";
			this.elementMetadata.set(data.id, {
				id: data.id,
				element: data.element,
				x: newX,
				y: newY,
				width: data.width,
				height: data.height,
				rotation: newRotation
			});
		}
		const handles = this.transformHandlesMap.get("multi-selection");
		if (handles) {
			handles.enableResize = false;
			const currentBounds = handles.bounds;
			if (currentBounds) {
				handles.bounds = {
					...currentBounds,
					rotation: this.lastMultiSelectionRotation ?? 0
				};
				handles.requestUpdate();
			}
		}
	}
	/**
	* Handle transform handles bounds-change event for single element.
	* Converts bounds from overlay-relative screen coordinates to canvas coordinates.
	*/
	handleTransformHandlesBoundsChange(elementId, bounds) {
		if (!this.overlayLayer) return;
		const canvasPos = {
			x: bounds.x,
			y: bounds.y
		};
		const canvasWidth = bounds.width;
		const canvasHeight = bounds.height;
		const element = this.elementRegistry.get(elementId);
		if (element) {
			this.updateElementPosition(elementId, canvasPos.x, canvasPos.y);
			const metadata = this.elementMetadata.get(elementId);
			if (metadata) {
				element.style.width = `${canvasWidth}px`;
				element.style.height = `${canvasHeight}px`;
				metadata.width = canvasWidth;
				metadata.height = canvasHeight;
			}
			if (bounds.rotation !== void 0 && bounds.rotation !== (metadata?.rotation || 0)) {
				element.style.transform = `rotate(${bounds.rotation}deg)`;
				if (metadata) metadata.rotation = bounds.rotation;
			}
		}
		const handles = this.transformHandlesMap.get(elementId);
		if (handles && this.overlayLayer) {
			const overlayRect = this.overlayLayer.getBoundingClientRect();
			const canvasRect = this.getBoundingClientRect();
			const scale = this.panZoomTransform?.scale || 1;
			const centerScreen = canvasToScreen(canvasPos.x + canvasWidth / 2, canvasPos.y + canvasHeight / 2, canvasRect, this.panZoomTransform);
			const screenWidth = canvasWidth * scale;
			const screenHeight = canvasHeight * scale;
			handles.bounds = {
				x: centerScreen.x - overlayRect.left - screenWidth / 2,
				y: centerScreen.y - overlayRect.top - screenHeight / 2,
				width: screenWidth,
				height: screenHeight,
				rotation: bounds.rotation || 0
			};
			handles.requestUpdate();
		}
	}
	/**
	* Handle transform handles rotation-change event for single element.
	*/
	handleTransformHandlesRotationChange(elementId, rotation) {
		const element = this.elementRegistry.get(elementId);
		if (!element) {
			console.warn("[EFCanvas] handleTransformHandlesRotationChange: element not found", elementId);
			return;
		}
		element.style.transform = `rotate(${rotation}deg)`;
		element.style.transformOrigin = "center";
		const metadata = this.elementMetadata.get(elementId);
		if (metadata) this.elementMetadata.set(elementId, {
			...metadata,
			rotation
		});
		else {
			this.updateElementMetadata(elementId);
			const updatedMetadata = this.elementMetadata.get(elementId);
			if (updatedMetadata) this.elementMetadata.set(elementId, {
				...updatedMetadata,
				rotation
			});
		}
	}
	/**
	* Cleanup transform handles.
	*/
	cleanupTransformHandles() {
		this.transformHandlesMap.forEach((handles) => {
			handles.remove();
		});
		this.transformHandlesMap.clear();
	}
	render() {
		return html`
      <div class="canvas-content">
        <slot></slot>
      </div>
    `;
	}
};
__decorate([consume({
	context: panZoomTransformContext,
	subscribe: true
})], EFCanvas.prototype, "panZoomTransform", void 0);
__decorate([property({
	type: String,
	attribute: "data-element-id-attribute"
})], EFCanvas.prototype, "elementIdAttribute", void 0);
__decorate([property({
	type: Boolean,
	attribute: "enable-transform-handles"
})], EFCanvas.prototype, "enableTransformHandles", void 0);
__decorate([property({ type: Boolean })], EFCanvas.prototype, "paused", void 0);
__decorate([state()], EFCanvas.prototype, "elementRegistry", void 0);
__decorate([state()], EFCanvas.prototype, "elementMetadata", void 0);
__decorate([state()], EFCanvas.prototype, "_activeRootTemporal", void 0);
__decorate([provide({ context: selectionContext }), state()], EFCanvas.prototype, "selectionContext", void 0);
__decorate([state()], EFCanvas.prototype, "highlightedElement", void 0);
EFCanvas = __decorate([customElement("ef-canvas")], EFCanvas);

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