import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
import { durationConverter } from "./durationConverter.js";
import { consume, createContext } from "@lit/context";
import { Task } from "@lit/task";
import { property, state } from "lit/decorators.js";
import _decorate from "@oxc-project/runtime/helpers/decorate";
const timegroupContext = createContext(Symbol("timeGroupContext"));
const isEFTemporal = (obj) => obj[EF_TEMPORAL];
const EF_TEMPORAL = Symbol("EF_TEMPORAL");
const deepGetElementsWithFrameTasks = (element, elements = []) => {
	for (const child of element.children) {
		if ("frameTask" in child && child.frameTask instanceof Task) elements.push(child);
		deepGetElementsWithFrameTasks(child, elements);
	}
	return elements;
};
let temporalCache;
const resetTemporalCache = () => {
	temporalCache = /* @__PURE__ */ new Map();
	if (typeof requestAnimationFrame !== "undefined") requestAnimationFrame(resetTemporalCache);
};
resetTemporalCache();
const shallowGetTemporalElements = (element, temporals = []) => {
	const cachedResult = temporalCache.get(element);
	if (cachedResult) return cachedResult;
	for (const child of element.children) if (isEFTemporal(child)) temporals.push(child);
	else shallowGetTemporalElements(child, temporals);
	temporalCache.set(element, temporals);
	return temporals;
};
var OwnCurrentTimeController = class {
	constructor(host, temporal) {
		this.host = host;
		this.temporal = temporal;
		host.addController(this);
	}
	hostUpdated() {
		this.temporal.requestUpdate("ownCurrentTimeMs");
	}
	remove() {
		this.host.removeController(this);
	}
};
let startTimeMsCache = /* @__PURE__ */ new WeakMap();
const resetStartTimeMsCache = () => {
	startTimeMsCache = /* @__PURE__ */ new WeakMap();
	if (typeof requestAnimationFrame !== "undefined") requestAnimationFrame(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();
			this.frameTask = new Task(this, {
				autoRun: EF_INTERACTIVE,
				args: () => [this.ownCurrentTimeMs],
				task: async ([], { signal: _signal }) => {
					let fullyUpdated = await this.updateComplete;
					while (!fullyUpdated) fullyUpdated = await this.updateComplete;
				}
			});
		}
		#parentTimegroup;
		set parentTimegroup(value) {
			this.#parentTimegroup = value;
			this.ownCurrentTimeController?.remove();
			this.rootTimegroup = this.getRootTimegroup();
			if (this.rootTimegroup) this.ownCurrentTimeController = new OwnCurrentTimeController(this.rootTimegroup, this);
		}
		disconnectedCallback() {
			super.disconnectedCallback();
			this.ownCurrentTimeController?.remove();
		}
		get parentTimegroup() {
			return this.#parentTimegroup;
		}
		set duration(value) {
			if (value !== void 0) this.setAttribute("duration", value);
			else this.removeAttribute("duration");
		}
		get trimStartMs() {
			if (this._trimStartMs === void 0) return void 0;
			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 void 0;
			return Math.min(this._trimEndMs, this.intrinsicDurationMs ?? 0);
		}
		set trimEndMs(value) {
			this._trimEndMs = value;
		}
		get sourceInMs() {
			if (this._sourceInMs === void 0) return void 0;
			return Math.max(this._sourceInMs, 0);
		}
		set sourceInMs(value) {
			this._sourceInMs = value;
		}
		get sourceOutMs() {
			if (this._sourceOutMs === void 0) return void 0;
			if (this.intrinsicDurationMs && this._sourceOutMs > this.intrinsicDurationMs) return this.intrinsicDurationMs;
			return Math.max(this._sourceOutMs, 0);
		}
		set sourceOutMs(value) {
			this._sourceOutMs = value;
		}
		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;
			return void 0;
		}
		get hasOwnDuration() {
			return false;
		}
		get intrinsicDurationMs() {
			return void 0;
		}
		get durationMs() {
			if (this.intrinsicDurationMs === void 0) return this._durationMs || this.parentTimegroup?.durationMs || 0;
			if (this.trimStartMs || this.trimEndMs) {
				const trimmedDurationMs = this.intrinsicDurationMs - (this.trimStartMs ?? 0) - (this.trimEndMs ?? 0);
				if (trimmedDurationMs < 0) return 0;
				return trimmedDurationMs;
			}
			if (this.sourceInMs || this.sourceOutMs) {
				const sourceInMs = this.sourceInMs ?? 0;
				const sourceOutMs = this.sourceOutMs ?? this.intrinsicDurationMs;
				if (sourceInMs >= sourceOutMs) return 0;
				return sourceOutMs - sourceInMs;
			}
			return this.intrinsicDurationMs;
		}
		get sourceStartMs() {
			return this.trimStartMs ?? this.sourceInMs ?? 0;
		}
		get offsetMs() {
			return this._offsetMs || 0;
		}
		get 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() {
			if (!this.parentTemporal) return 0;
			return this.startTimeMs - this.parentTemporal.startTimeMs;
		}
		get startTimeMs() {
			const cachedStartTime = startTimeMsCache.get(this);
			if (cachedStartTime !== void 0) return cachedStartTime;
			const parentTimegroup = this.parentTimegroup;
			if (!parentTimegroup) {
				startTimeMsCache.set(this, 0);
				return 0;
			}
			switch (parentTimegroup.mode) {
				case "sequence": {
					const siblingTemorals = shallowGetTemporalElements(parentTimegroup);
					const ownIndex = siblingTemorals?.indexOf(this);
					if (ownIndex === -1) return 0;
					if (ownIndex === 0) {
						startTimeMsCache.set(this, parentTimegroup.startTimeMs);
						return parentTimegroup.startTimeMs;
					}
					const previous = siblingTemorals?.[(ownIndex ?? 0) - 1];
					if (!previous) {
						console.error("Previous temporal element not found", {
							ownIndex,
							siblingTemorals
						});
						throw new Error("Previous temporal element not found");
					}
					startTimeMsCache.set(this, previous.startTimeMs + previous.durationMs - parentTimegroup.overlapMs);
					return previous.startTimeMs + previous.durationMs - parentTimegroup.overlapMs;
				}
				case "fit":
				case "contain":
				case "fixed":
					startTimeMsCache.set(this, parentTimegroup.startTimeMs + this.offsetMs);
					return parentTimegroup.startTimeMs + this.offsetMs;
				default: throw new Error(`Invalid time mode: ${parentTimegroup.mode}`);
			}
		}
		get endTimeMs() {
			return this.startTimeMs + this.durationMs;
		}
		/**
		* The current time of the element within itself.
		* Compare with `currentTimeMs` to see the current time with respect to the root timegroup
		*/
		get ownCurrentTimeMs() {
			if (this.rootTimegroup) return Math.min(Math.max(0, this.rootTimegroup.currentTimeMs - this.startTimeMs), this.durationMs);
			return 0;
		}
		/**
		* 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;
		}
		updated(changedProperties) {
			super.updated(changedProperties);
			if (changedProperties.has("currentTime") || changedProperties.has("ownCurrentTimeMs")) {
				const timelineTimeMs = (this.rootTimegroup ?? this).ownCurrentTimeMs;
				if (this.startTimeMs > timelineTimeMs || this.endTimeMs < timelineTimeMs) {
					this.style.display = "none";
					return;
				}
				this.style.display = "";
			}
		}
	}
	_decorate([consume({
		context: timegroupContext,
		subscribe: true
	}), property({ attribute: false })], TemporalMixinClass.prototype, "parentTimegroup", 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;
};
export { EFTemporal, deepGetElementsWithFrameTasks, flushStartTimeMsCache, isEFTemporal, shallowGetTemporalElements, timegroupContext };
