import { BaseMediaEngine } from "./BaseMediaEngine.js";
import { convertToScaledTime, roundToMilliseconds } from "./shared/PrecisionUtils.js";
var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
	constructor(host, src) {
		super(host);
		this.data = {};
		this.durationMs = 0;
		this.src = src;
	}
	static async fetch(host, urlGenerator, src) {
		const engine = new AssetMediaEngine(host, src);
		const url = urlGenerator.generateTrackFragmentIndexUrl(src);
		const data = await engine.fetchManifest(url);
		engine.data = data;
		const longestFragment = Object.values(engine.data).reduce((max, fragment) => Math.max(max, fragment.duration / fragment.timescale), 0);
		engine.durationMs = longestFragment * 1e3;
		if (src.startsWith("/")) engine.src = src.slice(1);
		return engine;
	}
	get audioTrackIndex() {
		return Object.values(this.data).find((track) => track.type === "audio");
	}
	get videoTrackIndex() {
		return Object.values(this.data).find((track) => track.type === "video");
	}
	get videoRendition() {
		return {
			trackId: this.videoTrackIndex?.track,
			src: this.src,
			startTimeOffsetMs: this.videoTrackIndex?.startTimeOffsetMs
		};
	}
	get audioRendition() {
		return {
			trackId: this.audioTrackIndex?.track,
			src: this.src
		};
	}
	get initSegmentPaths() {
		const paths = {};
		if (this.audioTrackIndex !== void 0) paths.audio = {
			path: `@ef-track/${this.audioTrackIndex.track}.m4s`,
			pos: this.audioTrackIndex.initSegment.offset,
			size: this.audioTrackIndex.initSegment.size
		};
		if (this.videoTrackIndex !== void 0) paths.video = {
			path: `/@ef-track/${this.videoTrackIndex.track}.m4s`,
			pos: this.videoTrackIndex.initSegment.offset,
			size: this.videoTrackIndex.initSegment.size
		};
		return paths;
	}
	get templates() {
		return {
			initSegment: "/@ef-track/{src}?trackId={trackId}",
			mediaSegment: "/@ef-track/{src}?trackId={trackId}"
		};
	}
	buildInitSegmentUrl(trackId) {
		return `/@ef-track/${this.src}?trackId=${trackId}`;
	}
	buildMediaSegmentUrl(trackId, segmentId) {
		return `/@ef-track/${this.src}?trackId=${trackId}&segmentId=${segmentId}`;
	}
	async fetchInitSegment(rendition, signal) {
		if (!rendition.trackId) throw new Error("Track ID is required for asset metadata");
		const url = this.buildInitSegmentUrl(rendition.trackId);
		const initSegment = this.data[rendition.trackId]?.initSegment;
		if (!initSegment) throw new Error("Init segment not found");
		const headers = { Range: `bytes=${initSegment.offset}-${initSegment.offset + initSegment.size - 1}` };
		return this.fetchMediaWithHeaders(url, headers, signal);
	}
	async fetchMediaSegment(segmentId, rendition, signal) {
		if (!rendition.trackId) throw new Error("Track ID is required for asset metadata");
		if (segmentId === void 0) throw new Error("Segment ID is not available");
		const url = this.buildMediaSegmentUrl(rendition.trackId, segmentId);
		const mediaSegment = this.data[rendition.trackId]?.segments[segmentId];
		if (!mediaSegment) throw new Error("Media segment not found");
		const headers = { Range: `bytes=${mediaSegment.offset}-${mediaSegment.offset + mediaSegment.size - 1}` };
		return this.fetchMediaWithHeaders(url, headers, signal);
	}
	/**
	* Calculate audio segments for variable-duration segments using track fragment index
	*/
	calculateAudioSegmentRange(fromMs, toMs, rendition, _durationMs) {
		if (fromMs >= toMs || !rendition.trackId) {
			console.warn(`calculateAudioSegmentRange: invalid fromMs ${fromMs} toMs ${toMs} rendition ${JSON.stringify(rendition)}`);
			return [];
		}
		const track = this.data[rendition.trackId];
		if (!track) {
			console.warn(`calculateAudioSegmentRange: track not found for rendition ${JSON.stringify(rendition)}`);
			return [];
		}
		const { timescale, segments } = track;
		const segmentRanges = [];
		for (let i = 0; i < segments.length; i++) {
			const segment = segments[i];
			const segmentStartTime = segment.cts;
			const segmentEndTime = segment.cts + segment.duration;
			const segmentStartMs = segmentStartTime / timescale * 1e3;
			const segmentEndMs = segmentEndTime / timescale * 1e3;
			if (segmentStartMs < toMs && segmentEndMs > fromMs) segmentRanges.push({
				segmentId: i,
				startMs: segmentStartMs,
				endMs: segmentEndMs
			});
		}
		if (segmentRanges.length === 0) console.warn(`calculateAudioSegmentRange: no segments found for fromMs ${fromMs} toMs ${toMs} rendition ${JSON.stringify({
			rendition,
			track
		})}`);
		return segmentRanges;
	}
	computeSegmentId(seekTimeMs, rendition) {
		if (!rendition.trackId) throw new Error("Track ID is required for asset metadata");
		const track = this.data[rendition.trackId];
		if (!track) throw new Error("Track not found");
		const { timescale, segments } = track;
		const startTimeOffsetMs = "startTimeOffsetMs" in rendition && rendition.startTimeOffsetMs || 0;
		const offsetSeekTimeMs = roundToMilliseconds(seekTimeMs + startTimeOffsetMs);
		const scaledSeekTime = convertToScaledTime(offsetSeekTimeMs, timescale);
		for (let i = segments.length - 1; i >= 0; i--) {
			const segment = segments[i];
			const segmentEndTime = segment.cts + segment.duration;
			if (segment.cts <= scaledSeekTime && scaledSeekTime < segmentEndTime) return i;
		}
		let nearestSegmentIndex = 0;
		let nearestDistance = Number.MAX_SAFE_INTEGER;
		for (let i = 0; i < segments.length; i++) {
			const segment = segments[i];
			const segmentStartTime = segment.cts;
			const segmentEndTime = segment.cts + segment.duration;
			let distance;
			if (scaledSeekTime < segmentStartTime) distance = segmentStartTime - scaledSeekTime;
			else if (scaledSeekTime >= segmentEndTime) distance = scaledSeekTime - segmentEndTime;
			else return i;
			if (distance < nearestDistance) {
				nearestDistance = distance;
				nearestSegmentIndex = i;
			}
		}
		return nearestSegmentIndex;
	}
};
export { AssetMediaEngine };
