import { PlaybackController } from "../gui/PlaybackController.js";
import { durationConverter } from "./durationConverter.js";
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
import { consume, createContext } from "@lit/context";
import { property, state } from "lit/decorators.js";

//#region src/elements/EFTemporal.ts
let isTimegroupCalculatingDurationFn = null;
const registerIsTimegroupCalculatingDuration = (fn) => {
	isTimegroupCalculatingDurationFn = fn;
};
const getIsTimegroupCalculatingDuration = () => {
	if (isTimegroupCalculatingDurationFn) return isTimegroupCalculatingDurationFn;
	let fallbackFn = () => false;
	try {
		const efTimegroupModule = globalThis.__EFTimegroupModule;
		if (efTimegroupModule?.isTimegroupCalculatingDuration) fallbackFn = efTimegroupModule.isTimegroupCalculatingDuration;
	} catch {}
	isTimegroupCalculatingDurationFn = fallbackFn;
	return fallbackFn;
};
const timegroupContext = createContext(Symbol("timeGroupContext"));
function determineTemporalRole(parentTimegroup) {
	return parentTimegroup === void 0 ? "root" : "child";
}
function determineDurationSource(intrinsicDurationMs, explicitDurationMs, parentDurationMs) {
	if (intrinsicDurationMs !== void 0) return {
		source: "intrinsic",
		baseDurationMs: intrinsicDurationMs
	};
	if (explicitDurationMs !== void 0) return {
		source: "explicit",
		baseDurationMs: explicitDurationMs
	};
	if (parentDurationMs !== void 0) return {
		source: "inherited",
		baseDurationMs: parentDurationMs
	};
	return {
		source: "inherited",
		baseDurationMs: 0
	};
}
function determineDurationModificationStrategy(trimStartMs, trimEndMs, sourceInMs, sourceOutMs) {
	if (trimStartMs !== void 0 || trimEndMs !== void 0) return {
		strategy: "trimming",
		trimStartMs,
		trimEndMs,
		sourceInMs: void 0,
		sourceOutMs: void 0
	};
	if (sourceInMs !== void 0 || sourceOutMs !== void 0) return {
		strategy: "source-manipulation",
		trimStartMs: void 0,
		trimEndMs: void 0,
		sourceInMs,
		sourceOutMs
	};
	return {
		strategy: "none",
		trimStartMs: void 0,
		trimEndMs: void 0,
		sourceInMs: void 0,
		sourceOutMs: void 0
	};
}
function evaluateModifiedDuration(baseDurationMs, modification) {
	if (baseDurationMs === 0) return 0;
	switch (modification.strategy) {
		case "trimming": {
			const trimmedDurationMs = baseDurationMs - (modification.trimStartMs ?? 0) - (modification.trimEndMs ?? 0);
			return Math.max(0, trimmedDurationMs);
		}
		case "source-manipulation": {
			const sourceInMs = modification.sourceInMs ?? 0;
			const sourceOutMs = modification.sourceOutMs ?? baseDurationMs;
			if (sourceInMs >= sourceOutMs) return 0;
			return sourceOutMs - sourceInMs;
		}
		case "none": return baseDurationMs;
	}
}
function determineStartTimeStrategy(parentTimegroup) {
	if (!parentTimegroup) return "offset";
	return parentTimegroup.mode === "sequence" ? "sequence" : "offset";
}
function evaluateStartTimeForSequence(_element, parentTimegroup, siblingTemporals, ownIndex) {
	if (ownIndex === 0) return parentTimegroup.startTimeMs;
	const previous = siblingTemporals[ownIndex - 1];
	if (!previous) throw new Error("Previous temporal element not found");
	return previous.startTimeMs + previous.durationMs - parentTimegroup.overlapMs;
}
function evaluateStartTimeForOffset(parentTimegroup, offsetMs) {
	return parentTimegroup.startTimeMs + offsetMs;
}
function evaluateStartTime(element, parentTimegroup, offsetMs, getSiblingTemporals) {
	if (!parentTimegroup) return 0;
	switch (determineStartTimeStrategy(parentTimegroup)) {
		case "sequence": {
			const siblingTemporals = getSiblingTemporals(parentTimegroup);
			const ownIndex = siblingTemporals.indexOf(element);
			if (ownIndex === -1) return 0;
			return evaluateStartTimeForSequence(element, parentTimegroup, siblingTemporals, ownIndex);
		}
		case "offset": return evaluateStartTimeForOffset(parentTimegroup, offsetMs);
	}
}
function determineCurrentTimeSource(playbackController, rootTimegroup, isRootTimegroup, localTimeMs, startTimeMs, durationMs) {
	if (playbackController) return {
		source: "playback-controller",
		timeMs: Math.min(Math.max(0, playbackController.currentTimeMs), durationMs)
	};
	if (rootTimegroup && !isRootTimegroup) return {
		source: "root-timegroup",
		timeMs: Math.min(Math.max(0, rootTimegroup.currentTimeMs - startTimeMs), durationMs)
	};
	return {
		source: "local",
		timeMs: Math.min(Math.max(0, localTimeMs), durationMs)
	};
}
const isEFTemporal = (obj) => obj[EF_TEMPORAL];
const EF_TEMPORAL = Symbol("EF_TEMPORAL");
const deepGetTemporalElements = (element, timeMs) => {
	const elements = [];
	const pruned = /* @__PURE__ */ new Set();
	const walk = (el) => {
		const children = getChildrenIncludingSlotted(el);
		for (const child of children) {
			if (isEFTemporal(child)) {
				const temporal = child;
				elements.push(temporal);
				if (timeMs !== void 0) {
					const startMs = temporal.startTimeMs;
					const endMs = temporal.endTimeMs;
					if (endMs > startMs && (timeMs < startMs || timeMs >= endMs)) {
						pruned.add(temporal);
						continue;
					}
				}
			}
			walk(child);
		}
	};
	walk(element);
	return {
		elements,
		pruned
	};
};
/**
* Gets all child elements including slotted content for shadow DOM elements.
* For elements with shadow DOM that contain slots, this returns the assigned
* elements (slotted content) instead of just the shadow DOM children.
*/
const getChildrenIncludingSlotted = (element) => {
	if (element.shadowRoot) {
		const slots = element.shadowRoot.querySelectorAll("slot");
		if (slots.length > 0) {
			const assignedElements = [];
			for (const slot of slots) assignedElements.push(...slot.assignedElements());
			for (const child of element.shadowRoot.children) if (child.tagName !== "SLOT") assignedElements.push(child);
			return assignedElements;
		}
	}
	return Array.from(element.children);
};
let temporalCache;
let temporalCacheResetScheduled = false;
const resetTemporalCache = () => {
	temporalCache = /* @__PURE__ */ new Map();
	if (typeof requestAnimationFrame !== "undefined" && !temporalCacheResetScheduled) {
		temporalCacheResetScheduled = true;
		requestAnimationFrame(() => {
			temporalCacheResetScheduled = false;
			resetTemporalCache();
		});
	}
};
resetTemporalCache();
const shallowGetTemporalElements = (element, temporals = []) => {
	const cachedResult = temporalCache.get(element);
	if (cachedResult) return cachedResult;
	const children = getChildrenIncludingSlotted(element);
	for (const child of children) if (isEFTemporal(child)) temporals.push(child);
	else shallowGetTemporalElements(child, temporals);
	temporalCache.set(element, temporals);
	return temporals;
};
var OwnCurrentTimeController = class {
	#lastKnownTimeMs = void 0;
	constructor(host, temporal) {
		this.host = host;
		this.temporal = temporal;
		host.addController(this);
	}
	hostUpdated() {
		const currentTimeMs = this.host.currentTimeMs;
		if (this.#lastKnownTimeMs === currentTimeMs) return;
		this.#lastKnownTimeMs = currentTimeMs;
		queueMicrotask(() => {
			this.temporal.requestUpdate("ownCurrentTimeMs");
		});
	}
	remove() {
		this.host.removeController(this);
	}
};
let startTimeMsCache = /* @__PURE__ */ new WeakMap();
let startTimeMsCacheResetScheduled = false;
const resetStartTimeMsCache = () => {
	startTimeMsCache = /* @__PURE__ */ new WeakMap();
	if (typeof requestAnimationFrame !== "undefined" && !startTimeMsCacheResetScheduled) {
		startTimeMsCacheResetScheduled = true;
		requestAnimationFrame(() => {
			startTimeMsCacheResetScheduled = false;
			resetStartTimeMsCache();
		});
	}
};
resetStartTimeMsCache();
const flushStartTimeMsCache = () => {
	startTimeMsCache = /* @__PURE__ */ new WeakMap();
};
const EFTemporal = (superClass) => {
	class TemporalMixinClass extends superClass {
		constructor(..._args) {
			super(..._args);
			this._offsetMs = 0;
			this._trimStartMs = void 0;
			this._trimEndMs = void 0;
			this._sourceInMs = void 0;
			this._sourceOutMs = void 0;
			this._startOffsetMs = 0;
			this.rootTimegroup = this.getRootTimegroup();
		}
		#contentReadyState = "idle";
		get contentReadyState() {
			return this.#contentReadyState;
		}
		set contentReadyState(value) {
			this.setContentReadyState(value);
		}
		setContentReadyState(state$1) {
			if (state$1 === this.#contentReadyState) return;
			const old = this.#contentReadyState;
			this.#contentReadyState = state$1;
			this.requestUpdate("contentReadyState", old);
			this.dispatchEvent(new CustomEvent("readystatechange", {
				detail: { state: state$1 },
				bubbles: false,
				composed: false
			}));
		}
		emitContentChange(reason) {
			this.dispatchEvent(new CustomEvent("contentchange", {
				detail: { reason },
				bubbles: false,
				composed: false
			}));
		}
		shouldAutoReady() {
			return true;
		}
		#ownCurrentTimeController;
		#parentTimegroup;
		#rootTimegroupLocked = false;
		set parentTimegroup(value) {
			const oldParent = this.#parentTimegroup;
			const oldRole = determineTemporalRole(oldParent);
			const newRole = determineTemporalRole(value);
			this.#parentTimegroup = value;
			this.#ownCurrentTimeController?.remove();
			if (!this.#rootTimegroupLocked) this.rootTimegroup = this.getRootTimegroup();
			if (this.rootTimegroup) this.#ownCurrentTimeController = new OwnCurrentTimeController(this.rootTimegroup, this);
			if (oldRole !== newRole) if (newRole === "root") this.didBecomeRoot();
			else this.didBecomeChild();
		}
		/**
		* Lock the rootTimegroup to prevent auto-recalculation.
		* Used for render clones where the root must be fixed.
		* @internal
		*/
		lockRootTimegroup() {
			this.#rootTimegroupLocked = true;
		}
		disconnectedCallback() {
			super.disconnectedCallback();
			if (this.canvasPreviewActive) return;
			this.#ownCurrentTimeController?.remove();
			if (this.playbackController) {
				this.playbackController.remove();
				this.playbackController = void 0;
			}
			import("./updateAnimations.js").then(({ cleanupTrackedAnimations }) => {
				cleanupTrackedAnimations(this);
			});
		}
		connectedCallback() {
			super.connectedCallback();
			if (this.canvasPreviewActive) return;
			this.#ownCurrentTimeController?.remove();
			if (!(this.parentElement?.closest("ef-timegroup") != null) && !this.playbackController) this.updateComplete.then(() => {
				if (!this.isConnected) return;
				if (!this.playbackController) this.didBecomeRoot();
			});
			if (this.shouldAutoReady()) this.updateComplete.then(() => {
				if (!this.isConnected) return;
				if (this.#contentReadyState === "idle") this.setContentReadyState("ready");
			});
		}
		get parentTimegroup() {
			return this.#parentTimegroup;
		}
		get playing() {
			if (!this.playbackController) return false;
			return this.playbackController.playing;
		}
		set playing(value) {
			if (!this.playbackController) {
				console.warn("Cannot set playing on non-root temporal element", this);
				return;
			}
			this.playbackController.setPlaying(value);
		}
		play() {
			if (!this.playbackController) {
				console.warn("play() called on non-root temporal element", this);
				return;
			}
			this.playbackController.play();
		}
		pause() {
			if (!this.playbackController) {
				console.warn("pause() called on non-root temporal element", this);
				return;
			}
			this.playbackController.pause();
		}
		get loop() {
			return this.playbackController?.loop ?? this.#loop;
		}
		set loop(value) {
			const oldValue = this.#loop;
			this.#loop = value;
			if (this.playbackController) this.playbackController.setLoop(value);
			this.requestUpdate("loop", oldValue);
		}
		set duration(value) {
			if (value !== void 0) this.setAttribute("duration", value);
			else this.removeAttribute("duration");
		}
		get trimStartMs() {
			if (this._trimStartMs === void 0) return;
			return Math.min(Math.max(this._trimStartMs, 0), this.intrinsicDurationMs ?? 0);
		}
		set trimStartMs(value) {
			this._trimStartMs = value;
		}
		get trimEndMs() {
			if (this._trimEndMs === void 0) return;
			return Math.min(this._trimEndMs, this.intrinsicDurationMs ?? 0);
		}
		set trimEndMs(value) {
			this._trimEndMs = value;
		}
		get sourceInMs() {
			if (this._sourceInMs === void 0) return;
			return Math.max(this._sourceInMs, 0);
		}
		set sourceInMs(value) {
			this._sourceInMs = value;
		}
		get sourceOutMs() {
			if (this._sourceOutMs === void 0) return;
			if (this.intrinsicDurationMs && this._sourceOutMs > this.intrinsicDurationMs) return this.intrinsicDurationMs;
			return Math.max(this._sourceOutMs, 0);
		}
		set sourceOutMs(value) {
			this._sourceOutMs = value;
		}
		updated(changedProperties) {
			super.updated?.(changedProperties);
			const sourceChanged = changedProperties.has("_sourceInMs") || changedProperties.has("_sourceOutMs");
			const trimChanged = changedProperties.has("_trimStartMs") || changedProperties.has("_trimEndMs");
			const becameReady = changedProperties.has("contentReadyState") && changedProperties.get("contentReadyState") !== "ready" && this.contentReadyState === "ready";
			if (sourceChanged || trimChanged || becameReady) {
				const isRenderClone = this.rootTimegroup?.hasAttribute("data-no-playback-controller");
				if (this.rootTimegroup && !isRenderClone) this.rootTimegroup.requestFrameRender();
				else if (this.playbackController) this.playbackController.runThrottledFrameTask();
			}
		}
		get startOffsetMs() {
			return this._startOffsetMs;
		}
		getRootTimegroup() {
			let parent = this.tagName === "EF-TIMEGROUP" ? this : this.parentTimegroup;
			while (parent?.parentTimegroup) parent = parent.parentTimegroup;
			return parent;
		}
		get hasExplicitDuration() {
			return this._durationMs !== void 0;
		}
		get explicitDurationMs() {
			if (this.hasExplicitDuration) return this._durationMs;
		}
		get hasOwnDuration() {
			return this.intrinsicDurationMs !== void 0 || this.hasExplicitDuration;
		}
		get intrinsicDurationMs() {}
		get durationMs() {
			const parentDurationMs = getIsTimegroupCalculatingDuration()(this.parentTimegroup) ? void 0 : this.parentTimegroup?.durationMs;
			const durationSource = determineDurationSource(this.intrinsicDurationMs, this._durationMs, parentDurationMs);
			const modification = determineDurationModificationStrategy(this.trimStartMs, this.trimEndMs, this.sourceInMs, this.sourceOutMs);
			return evaluateModifiedDuration(durationSource.baseDurationMs, modification);
		}
		get sourceStartMs() {
			return this.trimStartMs ?? this.sourceInMs ?? 0;
		}
		#offsetMs() {
			return this._offsetMs || 0;
		}
		#parentTemporal() {
			let parent = this.parentElement;
			while (parent && !isEFTemporal(parent)) parent = parent.parentElement;
			return parent;
		}
		/**
		* The start time of the element within its parent timegroup.
		*/
		get startTimeWithinParentMs() {
			const parent = this.#parentTemporal();
			if (!parent) return 0;
			return this.startTimeMs - parent.startTimeMs;
		}
		#loop = false;
		get startTimeMs() {
			const cachedStartTime = startTimeMsCache.get(this);
			if (cachedStartTime !== void 0) return cachedStartTime;
			const startTime = evaluateStartTime(this, this.parentTimegroup, this.#offsetMs(), (parent) => shallowGetTemporalElements(parent));
			startTimeMsCache.set(this, startTime);
			return startTime;
		}
		get endTimeMs() {
			return this.startTimeMs + this.durationMs;
		}
		#currentTimeMs = 0;
		/**
		* Set the base local time (ms) used by ownCurrentTimeMs when no playback
		* controller is present. Called by EFTimegroup.seekForRender() to keep the
		* mixin's internal time in sync with the timegroup's own time state.
		* @internal
		*/
		_setLocalTimeMs(value) {
			this.#currentTimeMs = value;
		}
		/**
		* The current time of the element within itself.
		* Compare with `currentTimeMs` to see the current time with respect to the root timegroup
		*/
		get ownCurrentTimeMs() {
			return determineCurrentTimeSource(this.playbackController, this.rootTimegroup, this.rootTimegroup === this, this.#currentTimeMs, this.startTimeMs, this.durationMs).timeMs;
		}
		/**
		* Element's current time for progress calculation.
		* Non-timegroup temporal elements use their local time (ownCurrentTimeMs)
		*/
		get currentTimeMs() {
			return this.ownCurrentTimeMs;
		}
		set currentTimeMs(value) {
			switch (determineTemporalRole(this.parentTimegroup)) {
				case "root":
					if (this.playbackController) this.playbackController.currentTime = value / 1e3;
					else {
						this.#currentTimeMs = value;
						this.requestUpdate("currentTimeMs");
					}
					break;
				case "child":
					if (this.rootTimegroup && this.rootTimegroup !== this) this.rootTimegroup.currentTimeMs = value;
					else {
						this.#currentTimeMs = value;
						this.requestUpdate("currentTimeMs");
					}
					break;
			}
		}
		/**
		* Used to calculate the internal currentTimeMs of the element. This is useful
		* for mapping to internal media time codes for audio/video elements.
		*/
		get currentSourceTimeMs() {
			const leadingTrimMs = this.sourceInMs || this.trimStartMs || 0;
			return this.ownCurrentTimeMs + leadingTrimMs;
		}
		didBecomeRoot() {
			const noPlayback = this.hasAttribute?.("data-no-playback-controller");
			const isRendering = typeof window !== "undefined" && "FRAMEGEN_BRIDGE" in window;
			if (noPlayback || this.playbackController || isRendering) return;
			this.playbackController = new PlaybackController(this);
			if (this.#loop) this.playbackController.setLoop(this.#loop);
		}
		didBecomeChild() {
			if (this.playbackController) {
				this.playbackController.remove();
				this.playbackController = void 0;
			}
		}
	}
	__decorate([property({
		type: String,
		reflect: true,
		attribute: "content-ready-state"
	})], TemporalMixinClass.prototype, "contentReadyState", null);
	__decorate([consume({
		context: timegroupContext,
		subscribe: true
	})], TemporalMixinClass.prototype, "parentTimegroup", null);
	__decorate([property({
		type: Boolean,
		reflect: true,
		attribute: "loop"
	})], TemporalMixinClass.prototype, "loop", null);
	__decorate([property({
		type: String,
		attribute: "offset",
		converter: durationConverter
	})], TemporalMixinClass.prototype, "_offsetMs", void 0);
	__decorate([property({
		type: Number,
		attribute: "duration",
		converter: durationConverter
	})], TemporalMixinClass.prototype, "_durationMs", void 0);
	__decorate([property({
		type: Number,
		attribute: "trimstart",
		converter: durationConverter
	})], TemporalMixinClass.prototype, "_trimStartMs", void 0);
	__decorate([property({
		type: Number,
		attribute: "trimend",
		converter: durationConverter
	})], TemporalMixinClass.prototype, "_trimEndMs", void 0);
	__decorate([property({
		type: Number,
		attribute: "sourcein",
		converter: durationConverter
	})], TemporalMixinClass.prototype, "_sourceInMs", void 0);
	__decorate([property({
		type: Number,
		attribute: "sourceout",
		converter: durationConverter
	})], TemporalMixinClass.prototype, "_sourceOutMs", void 0);
	__decorate([property({
		type: Number,
		attribute: "startoffset",
		converter: durationConverter
	})], TemporalMixinClass.prototype, "_startOffsetMs", void 0);
	__decorate([state()], TemporalMixinClass.prototype, "rootTimegroup", void 0);
	Object.defineProperty(TemporalMixinClass.prototype, EF_TEMPORAL, { value: true });
	return TemporalMixinClass;
};

//#endregion
export { EFTemporal, deepGetTemporalElements, flushStartTimeMsCache, isEFTemporal, registerIsTimegroupCalculatingDuration, resetTemporalCache, shallowGetTemporalElements, timegroupContext };
//# sourceMappingURL=EFTemporal.js.map