import { deepGetTemporalElements, isEFTemporal } from "./EFTemporal.js";

//#region src/elements/updateAnimations.ts
const ANIMATION_PRECISION_OFFSET = .1;
const DEFAULT_ANIMATION_ITERATIONS = 1;
const PROGRESS_PROPERTY = "--ef-progress";
const DURATION_PROPERTY = "--ef-duration";
const TRANSITION_DURATION_PROPERTY = "--ef-transition-duration";
const TRANSITION_OUT_START_PROPERTY = "--ef-transition-out-start";
/**
* Tracks animations per element to prevent them from being lost when they complete.
* Once an animation reaches 100% completion, it's removed from getAnimations(),
* but we keep a reference to it so we can continue controlling it.
*/
const animationTracker = /* @__PURE__ */ new WeakMap();
/**
* Tracks whether DOM structure has changed for an element, requiring animation rediscovery.
* For render clones (static DOM), this stays false after initial discovery.
* For prime timeline (interactive), this is set to true when mutations occur.
*/
const domStructureChanged = /* @__PURE__ */ new WeakMap();
/**
* Tracks the last known animation count for an element to detect new animations.
* Used as a lightweight check before calling expensive getAnimations().
*/
const lastAnimationCount = /* @__PURE__ */ new WeakMap();
/**
* Tracks which animations have already been validated to avoid duplicate warnings.
* Uses animation name + duration as the unique key.
*/
const validatedAnimations = /* @__PURE__ */ new Set();
/**
* Validates that an animation is still valid and controllable.
* Animations become invalid when:
* - They've been cancelled (idle state and not in getAnimations())
* - Their effect is null (animation was removed)
* - Their target is no longer in the DOM
*/
const isAnimationValid = (animation, currentAnimations) => {
	if (animation.playState === "idle" && !currentAnimations.includes(animation)) return false;
	const effect = animation.effect;
	if (!effect) return false;
	if (effect instanceof KeyframeEffect) {
		const target = effect.target;
		if (target && target instanceof Element) {
			if (!target.isConnected) return false;
		}
	}
	return true;
};
/**
* Discovers and tracks animations on an element and its subtree.
* This ensures we have references to animations even after they complete.
*
* Tracks animations per element where they exist, not just on the root element.
* This allows us to find animations on any element in the subtree.
*
* OPTIMIZATION: For render clones (static DOM), discovery happens once at creation.
* For prime timeline (interactive), discovery is responsive to DOM changes.
*
* Also cleans up invalid animations (cancelled, removed from DOM, etc.)
*
* @param providedAnimations - Optional pre-discovered animations to avoid redundant getAnimations() calls
*/
const discoverAndTrackAnimations = (element, providedAnimations) => {
	animationTracker.has(element);
	const structureChanged = domStructureChanged.get(element) ?? true;
	const currentAnimations = providedAnimations ?? element.getAnimations({ subtree: true });
	domStructureChanged.set(element, false);
	lastAnimationCount.set(element, currentAnimations.length);
	for (const animation of currentAnimations) {
		const effect = animation.effect;
		const target = effect && effect instanceof KeyframeEffect ? effect.target : null;
		if (target && target instanceof Element) {
			let tracked = animationTracker.get(target);
			if (!tracked) {
				tracked = /* @__PURE__ */ new Set();
				animationTracker.set(target, tracked);
			}
			tracked.add(animation);
		}
	}
	let rootTracked = animationTracker.get(element);
	if (!rootTracked) {
		rootTracked = /* @__PURE__ */ new Set();
		animationTracker.set(element, rootTracked);
	}
	for (const animation of currentAnimations) rootTracked.add(animation);
	for (const animation of rootTracked) if (!isAnimationValid(animation, currentAnimations)) rootTracked.delete(animation);
	const elementAnimationsMap = /* @__PURE__ */ new Map();
	for (const animation of currentAnimations) {
		const effect = animation.effect;
		const target = effect && effect instanceof KeyframeEffect ? effect.target : null;
		if (target && target instanceof Element) {
			let anims = elementAnimationsMap.get(target);
			if (!anims) {
				anims = [];
				elementAnimationsMap.set(target, anims);
			}
			anims.push(animation);
		}
	}
	if (structureChanged) for (const [el, tracked] of elementAnimationsMap) {
		const existingTracked = animationTracker.get(el);
		if (existingTracked) {
			for (const animation of existingTracked) if (!isAnimationValid(animation, tracked)) existingTracked.delete(animation);
			if (existingTracked.size === 0) animationTracker.delete(el);
		}
	}
	return {
		tracked: rootTracked,
		current: currentAnimations
	};
};
/**
* Cleans up tracked animations when an element is disconnected.
* This prevents memory leaks.
*/
const cleanupTrackedAnimations = (element) => {
	animationTracker.delete(element);
	domStructureChanged.delete(element);
	lastAnimationCount.delete(element);
};
/**
* Determines what phase an element is in relative to the timeline.
*
* WHY: Phase is the primary concept that drives all decisions. By explicitly
* enumerating phases, we make the code's logic clear: phase determines visibility,
* animation coordination, and visual state.
*
* Phases:
* - before-start: Timeline is before element's start time
* - active: Timeline is within element's active range (start to end, exclusive of end)
* - at-end-boundary: Timeline is exactly at element's end time
* - after-end: Timeline is after element's end time
*
* Note: We detect "at-end-boundary" by checking if timeline equals end time.
* The boundary policy will then determine if this should be treated as visible/active
* or not based on element characteristics.
*/
const determineElementPhase = (element, timelineTimeMs) => {
	const endTimeMs = element.endTimeMs;
	const startTimeMs = element.startTimeMs;
	if (endTimeMs <= startTimeMs) return "active";
	if (timelineTimeMs < startTimeMs) return "before-start";
	const epsilon = .001;
	const diff = timelineTimeMs - endTimeMs;
	if (diff > epsilon) return "after-end";
	if (Math.abs(diff) <= epsilon) return "at-end-boundary";
	return "active";
};
/**
* Visibility policy: determines when elements should be visible for display purposes.
*
* WHY: Root elements, elements aligned with composition end, and text segments
* should remain visible at exact end time to prevent flicker and show final frames.
* Other elements use exclusive end for clean transitions between elements.
*/
var VisibilityPolicy = class {
	shouldIncludeEndBoundary(element) {
		if (!element.parentTimegroup) return true;
		if (element.endTimeMs === element.rootTimegroup?.endTimeMs) return true;
		if (this.isTextSegment(element)) return true;
		return false;
	}
	/**
	* Checks if element is a text segment.
	* Encapsulates the tag name check to hide implementation detail.
	*/
	isTextSegment(element) {
		return element.tagName === "EF-TEXT-SEGMENT";
	}
};
const visibilityPolicy = new VisibilityPolicy();
/**
* Determines if an element should be visible based on its phase and visibility policy.
*/
const shouldBeVisible = (phase, element) => {
	if (phase === "before-start" || phase === "after-end") return false;
	if (phase === "active") return true;
	return visibilityPolicy.shouldIncludeEndBoundary(element);
};
/**
* Determines if animations should be coordinated based on element phase and animation policy.
*
* CRITICAL: Always returns true to support scrubbing to arbitrary times.
*
* Previously, this function skipped coordination for before-start and after-end phases as an
* optimization for live playback. However, this broke scrubbing scenarios where we seek to
* arbitrary times (timeline scrubbing, thumbnails, video export).
*
* The performance cost of always coordinating is minimal:
* - Animations only update when element time changes
* - Paused animation updates are optimized by the browser
* - The benefit is correct animation state at all times, regardless of phase
*/
const shouldCoordinateAnimations = (_phase, _element) => {
	return true;
};
/**
* Evaluates what the element's state should be based on the timeline.
*
* WHY: This function determines the complete temporal state including phase,
* which becomes the primary driver for all subsequent decisions.
*/
const evaluateTemporalState = (element) => {
	const timelineTimeMs = (element.rootTimegroup ?? element).currentTimeMs;
	const progress = element.durationMs <= 0 ? 1 : Math.max(0, Math.min(1, element.currentTimeMs / element.durationMs));
	const phase = determineElementPhase(element, timelineTimeMs);
	return {
		progress,
		isVisible: shouldBeVisible(phase, element),
		timelineTimeMs,
		phase
	};
};
/**
* Capability check: determines if an element supports stagger offset.
* Encapsulates the knowledge of which element types support this feature.
*/
const supportsStaggerOffset = (element) => {
	return element.tagName === "EF-TEXT-SEGMENT";
};
/**
* Calculates effective delay including stagger offset if applicable.
*
* Stagger offset allows elements (like text segments) to have their animations
* start at different times while keeping their visibility timing unchanged.
* This enables staggered animation effects within a single timegroup.
*/
const calculateEffectiveDelay = (delay, element) => {
	if (supportsStaggerOffset(element)) {
		const segment = element;
		if (segment.staggerOffsetMs !== void 0 && segment.staggerOffsetMs !== null) return delay + segment.staggerOffsetMs;
		let cssValue = element.style.getPropertyValue("--ef-stagger-offset").trim();
		if (!cssValue) cssValue = window.getComputedStyle(element).getPropertyValue("--ef-stagger-offset").trim();
		if (cssValue) {
			const match = cssValue.match(/(\d+(?:\.\d+)?)\s*ms?/);
			if (match) {
				const staggerOffset = parseFloat(match[1]);
				if (!isNaN(staggerOffset)) return delay + staggerOffset;
			} else {
				const numValue = parseFloat(cssValue);
				if (!isNaN(numValue)) return delay + numValue;
			}
		}
	}
	return delay;
};
/**
* Calculates maximum safe animation time to prevent completion.
*
* WHY: Once an animation reaches "finished" state, it can no longer be manually controlled
* via currentTime. By clamping to just before completion (using ANIMATION_PRECISION_OFFSET),
* we ensure the animation remains in a controllable state, allowing us to synchronize it
* with the timeline even when it would naturally be complete.
*/
const calculateMaxSafeAnimationTime = (duration, iterations) => {
	return duration * iterations - ANIMATION_PRECISION_OFFSET;
};
/**
* Determines if the current iteration should be reversed based on direction
*/
const shouldReverseIteration = (direction, currentIteration) => {
	return direction === "reverse" || direction === "alternate" && currentIteration % 2 === 1 || direction === "alternate-reverse" && currentIteration % 2 === 0;
};
/**
* Applies direction to iteration time (reverses if needed)
*/
const applyDirectionToIterationTime = (currentIterationTime, duration, direction, currentIteration) => {
	if (shouldReverseIteration(direction, currentIteration)) return duration - currentIterationTime;
	return currentIterationTime;
};
/**
* Maps element time to animation time for normal direction.
* Uses cumulative time throughout the animation.
* Note: elementTime should already be adjusted (elementTime - effectiveDelay).
*/
const mapNormalDirectionTime = (elementTime, duration, maxSafeTime) => {
	const currentIteration = Math.floor(elementTime / duration);
	const iterationTime = elementTime % duration;
	const cumulativeTime = currentIteration * duration + iterationTime;
	return Math.min(cumulativeTime, maxSafeTime);
};
/**
* Maps element time to animation time for reverse direction.
* Uses cumulative time with reversed iterations.
* Note: elementTime should already be adjusted (elementTime - effectiveDelay).
*/
const mapReverseDirectionTime = (elementTime, duration, maxSafeTime) => {
	const currentIteration = Math.floor(elementTime / duration);
	const reversedIterationTime = duration - elementTime % duration;
	const cumulativeTime = currentIteration * duration + reversedIterationTime;
	return Math.min(cumulativeTime, maxSafeTime);
};
/**
* Maps element time to animation time for alternate/alternate-reverse directions.
*
* WHY SPECIAL HANDLING: Alternate directions oscillate between forward and reverse iterations.
* Without delay, we use iteration time (0 to duration) because the animation naturally
* resets each iteration. However, with delay, iteration 0 needs to account for the delay
* offset (using ownCurrentTimeMs), and later iterations need cumulative time to properly
* track progress across multiple iterations. This complexity requires a dedicated mapper
* rather than trying to handle it in the general case.
*/
const mapAlternateDirectionTime = (elementTime, effectiveDelay, duration, direction, maxSafeTime) => {
	const adjustedTime = elementTime - effectiveDelay;
	if (effectiveDelay > 0) {
		if (Math.floor(adjustedTime / duration) === 0) return Math.min(elementTime, maxSafeTime);
		return Math.min(adjustedTime, maxSafeTime);
	}
	const currentIteration = Math.floor(elementTime / duration);
	const iterationTime = applyDirectionToIterationTime(elementTime % duration, duration, direction, currentIteration);
	return Math.min(iterationTime, maxSafeTime);
};
/**
* Maps element time to animation time based on direction.
*
* WHY: This function explicitly transforms element time to animation time, making
* the time mapping concept clear. Different directions require different transformations
* to achieve the desired visual effect.
*/
const mapElementTimeToAnimationTime = (elementTime, timing, effectiveDelay) => {
	const { duration, iterations, direction } = timing;
	const maxSafeTime = calculateMaxSafeAnimationTime(duration, iterations);
	const adjustedTime = elementTime - effectiveDelay;
	if (direction === "reverse") return mapReverseDirectionTime(adjustedTime, duration, maxSafeTime);
	if (direction === "alternate" || direction === "alternate-reverse") return mapAlternateDirectionTime(elementTime, effectiveDelay, duration, direction, maxSafeTime);
	return mapNormalDirectionTime(adjustedTime, duration, maxSafeTime);
};
/**
* Determines the animation time for a completed animation based on direction.
*/
const getCompletedAnimationTime = (timing, maxSafeTime) => {
	const { direction, iterations, duration } = timing;
	if (direction === "alternate" || direction === "alternate-reverse") {
		const finalIteration = iterations - 1;
		if (direction === "alternate" && finalIteration % 2 === 1 || direction === "alternate-reverse" && finalIteration % 2 === 0) return Math.min(duration - ANIMATION_PRECISION_OFFSET, maxSafeTime);
	}
	return maxSafeTime;
};
/**
* Validates that animation effect is a KeyframeEffect with a target
*/
const validateAnimationEffect = (effect) => {
	return effect !== null && effect instanceof KeyframeEffect && effect.target !== null;
};
/**
* Extracts timing information from an animation effect.
* Duration and delay from getTiming() are already in milliseconds.
* We use getTiming().delay directly from the animation object.
*/
const extractAnimationTiming = (effect) => {
	const timing = effect.getTiming();
	return {
		duration: Number(timing.duration) || 0,
		delay: Number(timing.delay) || 0,
		iterations: Number(timing.iterations) || DEFAULT_ANIMATION_ITERATIONS,
		direction: timing.direction || "normal"
	};
};
/**
* Analyzes keyframes to detect if animation is a fade-in or fade-out effect.
* Returns 'fade-in', 'fade-out', 'both', or null.
*/
const detectFadePattern = (keyframes) => {
	if (!keyframes || keyframes.length < 2) return null;
	const firstFrame = keyframes[0];
	const lastFrame = keyframes[keyframes.length - 1];
	const firstOpacity = firstFrame && "opacity" in firstFrame ? Number(firstFrame.opacity) : null;
	const lastOpacity = lastFrame && "opacity" in lastFrame ? Number(lastFrame.opacity) : null;
	if (firstOpacity === null || lastOpacity === null) return null;
	const isFadeIn = firstOpacity < lastOpacity;
	const isFadeOut = firstOpacity > lastOpacity;
	if (isFadeIn && isFadeOut) return "both";
	if (isFadeIn) return "fade-in";
	if (isFadeOut) return "fade-out";
	return null;
};
/**
* Analyzes keyframes to detect if animation has transform changes (slide, scale, etc).
*/
const hasTransformAnimation = (keyframes) => {
	if (!keyframes || keyframes.length < 2) return false;
	return keyframes.some((frame) => "transform" in frame || "translate" in frame || "scale" in frame || "rotate" in frame);
};
/**
* Validates CSS animation fill-mode to prevent flashing issues.
*
* CRITICAL: Editframe's timeline system pauses animations and manually controls them
* via animation.currentTime. This means elements exist in the DOM before their animations
* start. Without proper fill-mode, elements will "flash" to their natural state before
* the animation begins.
*
* Common issues:
* - Delayed animations without 'backwards': Element shows natural state during delay
* - Fade-in without 'backwards': Element visible before fade starts
* - Fade-out without 'forwards': Element snaps back after fade completes
*
* Only runs in development mode to avoid performance impact in production.
*/
const validateAnimationFillMode = (animation, timing) => {
	const effect = animation.effect;
	if (!validateAnimationEffect(effect)) return;
	const fill = effect.getTiming().fill || "none";
	const target = effect.target;
	let animationName = "unknown";
	if (animation.id) animationName = animation.id;
	else if (target instanceof HTMLElement) {
		const animationNameValue = window.getComputedStyle(target).animationName;
		if (animationNameValue && animationNameValue !== "none") animationName = animationNameValue.split(",")[0]?.trim() || "unknown";
	}
	const validationKey = `${animationName}-${timing.duration}`;
	if (validatedAnimations.has(validationKey)) return;
	validatedAnimations.add(validationKey);
	const warnings = [];
	if (timing.delay > 0 && fill !== "backwards" && fill !== "both") warnings.push(`⚠️  Animation "${animationName}" has a ${timing.delay}ms delay but no 'backwards' fill-mode.`, `   This will cause the element to show its natural state during the delay, then suddenly jump when the animation starts.`, `   Fix: Add 'backwards' or 'both' to the animation shorthand.`, `   Example: animation: ${animationName} ${timing.duration}ms ${timing.delay}ms backwards;`);
	try {
		const keyframes = effect.getKeyframes();
		const fadePattern = detectFadePattern(keyframes);
		const hasTransform = hasTransformAnimation(keyframes);
		if ((fadePattern === "fade-in" || hasTransform) && fill !== "backwards" && fill !== "both") warnings.push(`⚠️  Animation "${animationName}" modifies initial state but lacks 'backwards' fill-mode.`, `   The element will be visible in its natural state before the animation starts.`, `   Fix: Add 'backwards' or 'both' to the animation.`, `   Example: animation: ${animationName} ${timing.duration}ms backwards;`);
		if (fadePattern === "fade-out" && fill !== "forwards" && fill !== "both") warnings.push(`⚠️  Animation "${animationName}" modifies final state but lacks 'forwards' fill-mode.`, `   The element will snap back to its natural state after the animation completes.`, `   Fix: Add 'forwards' or 'both' to the animation.`, `   Example: animation: ${animationName} ${timing.duration}ms forwards;`);
		if (fadePattern === "both" && fill !== "both") warnings.push(`⚠️  Animation "${animationName}" modifies both initial and final state but doesn't use 'both' fill-mode.`, `   Fix: Use 'both' to apply initial and final states.`, `   Example: animation: ${animationName} ${timing.duration}ms both;`);
	} catch (e) {}
	if (warnings.length > 0 && typeof window !== "undefined" && window.__EDITFRAME_FILL_MODE_WARNINGS__) {
		console.groupCollapsed("%c🎬 Editframe Animation Fill-Mode Warning", "color: #f59e0b; font-weight: bold");
		warnings.forEach((warning) => console.log(warning));
		console.log("\n📚 Learn more: https://developer.mozilla.org/en-US/docs/Web/CSS/animation-fill-mode");
		console.groupEnd();
	}
};
/**
* Prepares animation for manual control by ensuring it's paused
*/
const prepareAnimation = (animation) => {
	if (animation.playState === "finished") animation.cancel();
	else if (animation.playState === "running") animation.pause();
};
/**
* Maps element time to animation currentTime and sets it on the animation.
*
* WHY: This function explicitly performs the time mapping transformation,
* making it clear that we're transforming element time to animation time.
*/
const mapAndSetAnimationTime = (animation, element, timing, effectiveDelay) => {
	const elementTime = element.ownCurrentTimeMs ?? 0;
	if (animation.playState === "running") animation.pause();
	const adjustedTime = elementTime - effectiveDelay;
	if (adjustedTime < 0) {
		if (timing.delay > 0) animation.currentTime = elementTime;
		else animation.currentTime = 0;
		return;
	}
	const { duration, iterations } = timing;
	const currentIteration = Math.floor(adjustedTime / duration);
	if (currentIteration >= iterations) {
		const completedAnimationTime = getCompletedAnimationTime(timing, calculateMaxSafeAnimationTime(duration, iterations));
		if (timing.delay > 0) animation.currentTime = effectiveDelay + completedAnimationTime;
		else animation.currentTime = completedAnimationTime;
	} else {
		const animationTime = mapElementTimeToAnimationTime(elementTime, timing, effectiveDelay);
		const { direction, delay } = timing;
		if (delay > 0) if ((direction === "alternate" || direction === "alternate-reverse") && effectiveDelay > 0 && currentIteration === 0) animation.currentTime = elementTime;
		else animation.currentTime = effectiveDelay + animationTime;
		else animation.currentTime = animationTime;
	}
};
/**
* Synchronizes a single animation with the timeline using the element as the time source.
*
* For animations in this element's subtree, always use this element as the time source.
* This handles both animations directly on the temporal element and on its non-temporal children.
*/
const synchronizeAnimation = (animation, element) => {
	const effect = animation.effect;
	if (!validateAnimationEffect(effect)) return;
	const timing = extractAnimationTiming(effect);
	if (timing.duration <= 0) {
		animation.currentTime = 0;
		return;
	}
	validateAnimationFillMode(animation, timing);
	const target = effect.target;
	let timeSource = element;
	if (target && target instanceof HTMLElement) {
		const nearestTimegroup = target.closest("ef-timegroup");
		if (nearestTimegroup && isEFTemporal(nearestTimegroup)) timeSource = nearestTimegroup;
	}
	let staggerElement = timeSource;
	if (target && target instanceof HTMLElement) {
		const targetAsAnimatable = target;
		if (supportsStaggerOffset(targetAsAnimatable)) staggerElement = targetAsAnimatable;
		else {
			const parentSegment = target.closest("ef-text-segment");
			if (parentSegment && supportsStaggerOffset(parentSegment)) staggerElement = parentSegment;
		}
	}
	const effectiveDelay = calculateEffectiveDelay(timing.delay, staggerElement);
	mapAndSetAnimationTime(animation, timeSource, timing, effectiveDelay);
};
/**
* Coordinates animations for a single element and its subtree, using the element as the time source.
*
* Uses tracked animations to ensure we can control animations even after they complete.
* Both CSS animations (created via the 'animation' property) and WAAPI animations are included.
*
* CRITICAL: CSS animations are created asynchronously when classes are added. This function
* discovers new animations on each call and tracks them in memory. Once animations complete,
* they're removed from getAnimations(), but we keep references to them so we can continue
* controlling them.
*/
const coordinateElementAnimations = (element, providedAnimations) => {
	const { tracked: trackedAnimations, current: currentAnimations } = discoverAndTrackAnimations(element, providedAnimations);
	for (const animation of trackedAnimations) {
		if (!isAnimationValid(animation, currentAnimations)) continue;
		prepareAnimation(animation);
		synchronizeAnimation(animation, element);
	}
};
/**
* Applies visual state (CSS + display) to match temporal state.
*
* WHY: This function applies visual state based on the element's phase and state.
* Phase determines what should be visible, and this function applies that decision.
*/
const applyVisualState = (element, state) => {
	element.style.setProperty(PROGRESS_PROPERTY, `${state.progress}`);
	if (!state.isVisible) {
		element.style.setProperty("display", "none");
		return;
	}
	element.style.removeProperty("display");
	element.style.setProperty(DURATION_PROPERTY, `${element.durationMs}ms`);
	element.style.setProperty(TRANSITION_DURATION_PROPERTY, `${element.parentTimegroup?.overlapMs ?? 0}ms`);
	element.style.setProperty(TRANSITION_OUT_START_PROPERTY, `${element.durationMs - (element.parentTimegroup?.overlapMs ?? 0)}ms`);
};
/**
* Applies animation coordination if the element phase requires it.
*
* WHY: Animation coordination is driven by phase. If the element is in a phase
* where animations should be coordinated, we coordinate them.
*/
const applyAnimationCoordination = (element, phase, providedAnimations) => {
	if (shouldCoordinateAnimations(phase, element)) coordinateElementAnimations(element, providedAnimations);
};
/**
* Finds the nearest temporal ancestor (or self) for a given element.
* Returns the element itself if it is a temporal element, otherwise walks up.
* Falls back to the provided root element.
*/
const findNearestTemporalAncestor = (element, root) => {
	let node = element;
	while (node) {
		if (isEFTemporal(node)) return node;
		node = node.parentElement;
	}
	return root;
};
/**
* Synchronizes all SVG SMIL animations in the subtree with the timeline.
*
* SVG has its own animation clock on each SVGSVGElement. We pause it and
* seek it to the owning temporal element's current time (converted to seconds).
*/
const synchronizeSvgAnimations = (root) => {
	const svgElements = root.querySelectorAll("svg");
	for (const svg of svgElements) {
		const timeMs = findNearestTemporalAncestor(svg, root).currentTimeMs ?? 0;
		svg.setCurrentTime(timeMs / 1e3);
		svg.pauseAnimations();
	}
};
/**
* Synchronizes all <video> and <audio> elements in the subtree with the timeline.
*
* Sets currentTime (in seconds) to match the owning temporal element's position
* and ensures the elements are paused (playback is controlled by the timeline).
*/
const synchronizeMediaElements = (root) => {
	const mediaElements = root.querySelectorAll("video, audio");
	for (const media of mediaElements) {
		const timeSec = (findNearestTemporalAncestor(media, root).currentTimeMs ?? 0) / 1e3;
		if (media.currentTime !== timeSec) media.currentTime = timeSec;
		if (!media.paused) media.pause();
	}
};
/**
* Evaluates the complete state for an element update.
* This separates evaluation (what should the state be?) from application (apply that state).
*/
const evaluateElementState = (element) => {
	return {
		element,
		state: evaluateTemporalState(element)
	};
};
/**
* Main function: synchronizes DOM element with timeline.
*
* Orchestrates clear flow: Phase → Policy → Time Mapping → State Application
*
* WHY: This function makes the conceptual flow explicit:
* 1. Determine phase (what phase is the element in?)
* 2. Apply policies (should it be visible/coordinated based on phase?)
* 3. Map time for animations (transform element time to animation time)
* 4. Apply visual state (update CSS and display based on phase and policies)
*/
const updateAnimations = (element) => {
	const allAnimations = element.getAnimations({ subtree: true });
	const rootContext = evaluateElementState(element);
	const timelineTimeMs = (element.rootTimegroup ?? element).currentTimeMs;
	const { elements: collectedElements, pruned } = deepGetTemporalElements(element, timelineTimeMs);
	for (const prunedElement of pruned) prunedElement.style.setProperty("display", "none");
	const childContexts = [];
	for (const temporalElement of collectedElements) if (!pruned.has(temporalElement)) childContexts.push(evaluateElementState(temporalElement));
	const visibleChildContexts = [];
	for (const ctx of childContexts) if (shouldBeVisible(ctx.state.phase, ctx.element)) visibleChildContexts.push(ctx);
	const temporalSet = new Set(visibleChildContexts.map((c) => c.element));
	temporalSet.add(element);
	const childAnimations = /* @__PURE__ */ new Map();
	for (const animation of allAnimations) {
		const effect = animation.effect;
		const target = effect && effect instanceof KeyframeEffect ? effect.target : null;
		if (!target || !(target instanceof Element)) continue;
		let node = target;
		while (node) {
			if (temporalSet.has(node)) {
				let anims = childAnimations.get(node);
				if (!anims) {
					anims = [];
					childAnimations.set(node, anims);
				}
				anims.push(animation);
				break;
			}
			node = node.parentElement;
		}
	}
	applyAnimationCoordination(rootContext.element, rootContext.state.phase, allAnimations);
	for (const context of visibleChildContexts) applyAnimationCoordination(context.element, context.state.phase, childAnimations.get(context.element) || []);
	applyVisualState(rootContext.element, rootContext.state);
	for (const context of childContexts) applyVisualState(context.element, context.state);
	synchronizeSvgAnimations(element);
	synchronizeMediaElements(element);
};

//#endregion
export { cleanupTrackedAnimations, updateAnimations };
//# sourceMappingURL=updateAnimations.js.map