import { CachedFetcher } from "./CachedFetcher.js";
import { createFragmentIndex, createManifestIndex } from "./SegmentIndex.js";
import { createByteRangeTransport, createUrlTransport } from "./SegmentTransport.js";
import { createByteRangeTiming, createJitTiming } from "./TimingModel.js";

//#region src/elements/EFMedia/MediaEngine.ts
function createMediaEngine(index, transport, timing, src) {
	return {
		durationMs: index.durationMs,
		src,
		index,
		transport,
		timing,
		tracks: index.tracks,
		async extractThumbnails(timestamps, signal) {
			const track = index.tracks.video ?? index.tracks.scrub;
			if (!track) return timestamps.map(() => null);
			const { ThumbnailExtractor } = await import("./shared/ThumbnailExtractor.js");
			return new ThumbnailExtractor(this).extractThumbnails(timestamps, track, index.durationMs, signal);
		}
	};
}
async function fetchFileIndex(fetchFn, fileId, apiHost, signal) {
	const response = await fetchFn(`${apiHost}/api/v1/files/${fileId}/index`, { signal });
	signal?.throwIfAborted();
	const contentType = response.headers.get("content-type");
	if (!response.ok || contentType && !contentType.includes("application/json")) {
		const text = await response.clone().text();
		if (!response.ok) throw new Error(`Failed to fetch asset index: ${response.status} ${text}`);
		throw new Error(`Expected JSON but got ${contentType}: ${text.substring(0, 100)}`);
	}
	try {
		const data = await response.json();
		signal?.throwIfAborted();
		return data;
	} catch (error) {
		if (error instanceof DOMException && error.name === "AbortError") throw error;
		throw new Error(`Failed to parse JSON response: ${error instanceof Error ? error.message : String(error)}`);
	}
}
async function validateTrackAccess(transport, tracks, requiredTracks, signal) {
	if (!signal) return;
	const toValidate = [];
	const needsVideo = requiredTracks === "video" || requiredTracks === "both";
	const needsAudio = requiredTracks === "audio" || requiredTracks === "both";
	if (needsVideo && tracks.video) toValidate.push(tracks.video);
	if (needsAudio && tracks.audio) toValidate.push(tracks.audio);
	for (const track of toValidate) {
		signal.throwIfAborted();
		try {
			await transport.fetchInitSegment(track, signal);
		} catch (error) {
			if (error instanceof DOMException && error.name === "AbortError") throw error;
			if (error instanceof Error && (error.message.includes("401") || error.message.includes("UNAUTHORIZED") || error.message.includes("Failed to fetch") && error.message.includes("401"))) throw new Error(`${track.role} segments require authentication: ${error.message}`);
		}
	}
}
function buildEngineComponents(indexData, fetcher) {
	switch (indexData.type) {
		case "fragment": return {
			index: createFragmentIndex(indexData.data, indexData.src),
			transport: createByteRangeTransport(indexData.data, indexData.fileId, indexData.apiHost, fetcher),
			timing: createByteRangeTiming(indexData.data),
			src: indexData.src
		};
		case "manifest": return {
			index: createManifestIndex(indexData.data),
			transport: createUrlTransport({
				fetcher,
				src: indexData.data.sourceUrl,
				templates: indexData.data.endpoints,
				audioTrackId: void 0,
				videoTrackId: void 0
			}),
			timing: createJitTiming(),
			src: indexData.data.sourceUrl
		};
	}
}
async function createMediaEngineFromSource(opts) {
	const { src, fileId, apiHost, requiredTracks, fetchFn, urlGenerator, signal } = opts;
	const fetcher = new CachedFetcher(fetchFn);
	let indexData;
	if (fileId !== null && fileId !== void 0 && fileId.trim() !== "") {
		if (!apiHost) throw new Error("API host is required for file-id mode");
		const data = await fetchFileIndex(fetchFn, fileId, apiHost, signal);
		signal?.throwIfAborted();
		indexData = {
			type: "fragment",
			data,
			src: fileId,
			apiHost,
			fileId
		};
	} else if (!src || typeof src !== "string" || src.trim() === "") return;
	else {
		const manifestSrc = resolveManifestSrc(src, apiHost);
		const url = urlGenerator.generateManifestUrl(manifestSrc);
		const manifest = await fetcher.fetchJson(url, signal);
		signal?.throwIfAborted();
		indexData = {
			type: "manifest",
			data: manifest,
			src: manifest.sourceUrl
		};
	}
	const { index, transport, timing, src: engineSrc } = buildEngineComponents(indexData, fetcher);
	await validateTrackAccess(transport, index.tracks, requiredTracks, signal);
	return createMediaEngine(index, transport, timing, engineSrc);
}
/**
* Resolve a src value to the URL the server should transcode.
* - Remote URLs (http/https) pass through as-is
* - Local paths are made absolute using apiHost when available
*/
function resolveManifestSrc(src, apiHost) {
	const lower = src.toLowerCase();
	if (lower.startsWith("http://") || lower.startsWith("https://")) return src;
	if (apiHost) {
		const base = apiHost.replace(/\/$/, "");
		const normalizedPath = src.replace(/^\.\//, "/src/");
		return `${base}${normalizedPath.startsWith("/") ? "" : "/"}${normalizedPath}`;
	}
	return src;
}

//#endregion
export { createMediaEngineFromSource };
//# sourceMappingURL=MediaEngine.js.map