import { RequestDeduplicator } from "../../transcoding/cache/RequestDeduplicator.js";
import { SizeAwareLRUCache } from "../../utils/LRUCache.js";
const mediaCache = new SizeAwareLRUCache(100 * 1024 * 1024);
const globalRequestDeduplicator = new RequestDeduplicator();
var BaseMediaEngine = class {
	constructor(host) {
		this.host = host;
	}
	getVideoRendition() {
		if (!this.videoRendition) throw new Error("No video rendition available");
		return this.videoRendition;
	}
	getAudioRendition() {
		if (!this.audioRendition) throw new Error("No audio rendition available");
		return this.audioRendition;
	}
	/**
	* Generate cache key for segment requests
	*/
	getSegmentCacheKey(segmentId, rendition) {
		return `${rendition.src}-${rendition.id}-${segmentId}-${rendition.trackId}`;
	}
	/**
	* Unified fetch method with caching and global deduplication
	* All requests (media, manifest, init segments) go through this method
	*/
	async fetchWithCache(url, options) {
		const { responseType, headers, signal } = options;
		const cacheKey = headers ? `${url}:${JSON.stringify(headers)}` : url;
		const cached = mediaCache.get(cacheKey);
		if (cached) {
			if (signal) return this.handleAbortForCachedRequest(cached, signal);
			return cached;
		}
		const promise = globalRequestDeduplicator.executeRequest(cacheKey, async () => {
			try {
				const response = await this.host.fetch(url, { headers });
				if (responseType === "json") return response.json();
				return response.arrayBuffer();
			} catch (error) {
				if (error instanceof DOMException && error.name === "AbortError") mediaCache.delete(cacheKey);
				throw error;
			}
		});
		mediaCache.set(cacheKey, promise);
		promise.catch((error) => {
			if (error instanceof DOMException && error.name === "AbortError") mediaCache.delete(cacheKey);
		});
		if (signal) return this.handleAbortForCachedRequest(promise, signal);
		return promise;
	}
	/**
	* Handles abort logic for a cached request without affecting the underlying fetch
	* This allows multiple instances to share the same cached request while each
	* manages their own abort behavior
	*/
	handleAbortForCachedRequest(promise, signal) {
		if (signal.aborted) throw new DOMException("Aborted", "AbortError");
		return Promise.race([promise, new Promise((_, reject) => {
			signal.addEventListener("abort", () => {
				reject(new DOMException("Aborted", "AbortError"));
			});
		})]);
	}
	async fetchMedia(url, signal) {
		if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
		return this.fetchWithCache(url, {
			responseType: "arrayBuffer",
			signal
		});
	}
	async fetchManifest(url, signal) {
		if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
		return this.fetchWithCache(url, {
			responseType: "json",
			signal
		});
	}
	async fetchMediaWithHeaders(url, headers, signal) {
		if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
		return this.fetchWithCache(url, {
			responseType: "arrayBuffer",
			headers,
			signal
		});
	}
	async fetchMediaCache(url, signal) {
		return this.fetchMedia(url, signal);
	}
	async fetchManifestCache(url, signal) {
		return this.fetchManifest(url, signal);
	}
	async fetchMediaCacheWithHeaders(url, headers, signal) {
		return this.fetchMediaWithHeaders(url, headers, signal);
	}
	/**
	* Fetch media segment with built-in deduplication
	* Now uses global deduplication for all requests
	*/
	async fetchMediaSegmentWithDeduplication(segmentId, rendition, _signal) {
		const cacheKey = this.getSegmentCacheKey(segmentId, rendition);
		return globalRequestDeduplicator.executeRequest(cacheKey, async () => {
			return this.fetchMediaSegment(segmentId, rendition);
		});
	}
	/**
	* Check if a segment is currently being fetched
	*/
	isSegmentBeingFetched(segmentId, rendition) {
		const cacheKey = this.getSegmentCacheKey(segmentId, rendition);
		return globalRequestDeduplicator.isPending(cacheKey);
	}
	/**
	* Get count of active segment requests (for debugging/monitoring)
	*/
	getActiveSegmentRequestCount() {
		return globalRequestDeduplicator.getPendingCount();
	}
	/**
	* Cancel all active segment requests (for cleanup)
	*/
	cancelAllSegmentRequests() {
		globalRequestDeduplicator.clear();
	}
	/**
	* Calculate audio segments needed for a time range
	* Each media engine implements this based on their segment structure
	*/
	calculateAudioSegmentRange(fromMs, toMs, rendition, durationMs) {
		if (fromMs >= toMs) return [];
		const segments = [];
		if (rendition.segmentDurationsMs && rendition.segmentDurationsMs.length > 0) {
			let cumulativeTime = 0;
			for (let i = 0; i < rendition.segmentDurationsMs.length; i++) {
				const segmentDuration = rendition.segmentDurationsMs[i];
				if (segmentDuration === void 0) continue;
				const segmentStartMs = cumulativeTime;
				const segmentEndMs = Math.min(cumulativeTime + segmentDuration, durationMs);
				if (segmentStartMs >= durationMs) break;
				if (segmentStartMs < toMs && segmentEndMs > fromMs) segments.push({
					segmentId: i + 1,
					startMs: segmentStartMs,
					endMs: segmentEndMs
				});
				cumulativeTime += segmentDuration;
				if (cumulativeTime >= durationMs) break;
			}
			return segments;
		}
		const segmentDurationMs = rendition.segmentDurationMs || 1e3;
		const startSegmentIndex = Math.floor(fromMs / segmentDurationMs);
		const endSegmentIndex = Math.floor(toMs / segmentDurationMs);
		for (let i = startSegmentIndex; i <= endSegmentIndex; i++) {
			const segmentId = i + 1;
			const segmentStartMs = i * segmentDurationMs;
			const segmentEndMs = Math.min((i + 1) * segmentDurationMs, durationMs);
			if (segmentStartMs >= durationMs) break;
			if (segmentStartMs < toMs && segmentEndMs > fromMs) segments.push({
				segmentId,
				startMs: segmentStartMs,
				endMs: segmentEndMs
			});
		}
		return segments;
	}
};
export { BaseMediaEngine };
