import { clearCurrentFrameSpan, enableTracing, extractParentContext, setCurrentFrameSpan, withSpan, withSpanAndContext } from "./otel/tracingHelpers.js";
import { shallowGetTimegroups } from "./elements/EFTimegroup.js";
import { setupTemporalHierarchy } from "./elements/setupTemporalHierarchy.js";
import { setupBrowserTracing } from "./otel/setupBrowserTracing.js";

//#region src/EF_FRAMEGEN.ts
var TriggerCanvas = class {
	constructor() {
		this.canvasInitialized = false;
		this.canvas = document.createElement("canvas");
		const ctx = this.canvas.getContext("2d", { willReadFrequently: true });
		if (!ctx) throw new Error("Canvas 2d context not ready");
		this.ctx = ctx;
		this.ctx.fillStyle = "transparent";
	}
	initialize() {
		if (this.canvasInitialized) return;
		this.canvasInitialized = true;
		this.canvas.width = 1;
		this.canvas.height = 1;
		Object.assign(this.canvas.style, {
			position: "fixed",
			top: "0px",
			left: "0px",
			width: "100%",
			height: "100%",
			zIndex: "100000"
		});
		document.body.appendChild(this.canvas);
	}
	trigger() {
		this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
	}
};
var EFFramegen = class {
	trace(...args) {
		console.trace("[EF_FRAMEGEN]", ...args);
	}
	async syncLog(...args) {
		if (!this.BRIDGE) {
			console.log("[EF_FRAMEGEN]", ...args);
			return;
		}
		const sequence = ++this.logSequence;
		const message = args.map((arg) => typeof arg === "object" ? JSON.stringify(arg) : String(arg)).join(" ");
		return new Promise((resolve) => {
			this.BRIDGE.syncLog(sequence, message, () => {
				resolve();
			});
		});
	}
	initializeVerificationCanvas() {
		if (this.verificationCanvas) return;
		this.verificationCanvas = document.createElement("canvas");
		const ctx = this.verificationCanvas.getContext("2d");
		if (!ctx) throw new Error("Verification canvas 2d context not ready");
		this.verificationCtx = ctx;
		const workbench = document.querySelector("ef-workbench");
		const canvasWidth = workbench ? workbench.clientWidth : this.renderOptions?.encoderOptions.video.width ?? 0;
		if (canvasWidth > 0) {
			this.verificationCanvas.width = canvasWidth;
			this.verificationCanvas.height = 1;
			Object.assign(this.verificationCanvas.style, {
				position: "fixed",
				left: "0px",
				bottom: "0px",
				width: `${canvasWidth}px`,
				height: "1px",
				zIndex: "99999"
			});
			document.body.appendChild(this.verificationCanvas);
		}
	}
	drawVerificationStrip(frameNumber) {
		this.initializeVerificationCanvas();
		if (!this.verificationCanvas || !this.verificationCtx) return;
		const width = this.verificationCanvas.width;
		const height = this.verificationCanvas.height;
		this.verificationCtx.clearRect(0, 0, width, height);
		const red = Math.floor(frameNumber / (256 * 256)) % 256;
		const green = Math.floor(frameNumber / 256) % 256;
		const blue = frameNumber % 256;
		this.verificationCtx.fillStyle = `rgb(${red}, ${green}, ${blue})`;
		this.verificationCtx.fillRect(0, 0, width, height);
	}
	constructor() {
		this.time = 0;
		this.frameDurationMs = 0;
		this.frameBox = document.createElement("div");
		this.triggerCanvas = new TriggerCanvas();
		this.logSequence = 0;
		this.frameTasksInProgress = false;
		this.currentFrameNumber = 0;
		this.timingFrameCount = 0;
		this.timingAccum = {
			updateComplete1Ms: 0,
			updateComplete2Ms: 0,
			updateComplete3Ms: 0,
			textSegmentsMs: 0,
			renderFrameMs: 0,
			renderFrameQueryMs: 0,
			renderFramePrepareMs: 0,
			renderFrameDrawMs: 0,
			renderFrameAnimsMs: 0,
			frameTasksMs: 0,
			totalMs: 0
		};
		this.BRIDGE = window.FRAMEGEN_BRIDGE;
		if (this.BRIDGE) this.connectToBridge();
	}
	/**
	* Helper method to get the workbench and set its rendering state.
	* This ensures consistent state management across the framegen lifecycle.
	*/
	setWorkbenchRendering(isRendering) {
		const workbench = document.querySelector("ef-workbench");
		if (workbench) workbench.rendering = isRendering;
	}
	connectToBridge() {
		const BRIDGE = this.BRIDGE;
		if (!BRIDGE) throw new Error("No BRIDGE when attempting to connect to bridge");
		BRIDGE.onInitialize(async (renderOptions, traceContext, otelEndpoint) => {
			if (renderOptions.enableTracing && otelEndpoint) {
				enableTracing();
				await setupBrowserTracing({
					otelEndpoint,
					serviceName: "telecine-browser",
					bridge: BRIDGE,
					useBatching: true
				});
			}
			const parentContext = extractParentContext(traceContext);
			await withSpan("browser.initialize", {
				width: renderOptions.encoderOptions.video.width,
				height: renderOptions.encoderOptions.video.height,
				fps: renderOptions.encoderOptions.video.framerate,
				durationMs: renderOptions.encoderOptions.toMs - renderOptions.encoderOptions.fromMs
			}, parentContext, async () => {
				try {
					await this.initialize(renderOptions);
				} catch (error) {
					this.setWorkbenchRendering(false);
					console.error("[EF_FRAMEGEN.connectToBridge] error initializing", error);
					throw error;
				}
			});
			BRIDGE.initialized();
		});
		BRIDGE.onBeginFrame((frameNumber, isLast, traceContext) => {
			const parentContext = extractParentContext(traceContext);
			withSpanAndContext("browser.frame.render", {
				frameNumber,
				isLast
			}, parentContext, async (span, _spanContext) => {
				setCurrentFrameSpan(span);
				try {
					await this.beginFrame(frameNumber, isLast);
				} catch (error) {
					this.setWorkbenchRendering(false);
					throw error;
				} finally {
					clearCurrentFrameSpan();
				}
			}).catch((error) => {
				console.error("[EF_FRAMEGEN.beginFrame] error:", error);
				this.setWorkbenchRendering(false);
				clearCurrentFrameSpan();
				throw error;
			});
		});
		BRIDGE.onTriggerCanvas((traceContext) => {
			withSpan("browser.canvas.trigger", {}, extractParentContext(traceContext), async () => {
				this.triggerCanvas.trigger();
			}).catch((error) => {
				console.error("[EF_FRAMEGEN.triggerCanvas] error:", error);
			});
		});
	}
	get showFrameBox() {
		return this.renderOptions?.showFrameBox ?? false;
	}
	async initialize(renderOptions) {
		this.renderOptions = renderOptions;
		const workbench = document.querySelector("ef-workbench");
		if (workbench) {
			this.setWorkbenchRendering(true);
			workbench.playing = false;
		}
		const searchRoot = workbench || document.body;
		const firstGroup = shallowGetTimegroups(searchRoot)[0];
		if (!firstGroup) throw new Error("No temporal elements found");
		const startingTimeMs = renderOptions.encoderOptions.fromMs;
		await firstGroup.waitForMediaDurations();
		setupTemporalHierarchy(searchRoot, firstGroup);
		firstGroup.setAttribute("data-no-playback-controller", "");
		await firstGroup.seekForRender(startingTimeMs);
		this.frameDurationMs = 1e3 / renderOptions.encoderOptions.video.framerate;
		this.time = startingTimeMs;
		if (this.showFrameBox) {
			Object.assign(this.frameBox.style, {
				width: "200px",
				height: "100px",
				font: "10px Arial",
				backgroundColor: "white",
				position: "absolute",
				top: "0px",
				right: "0px",
				zIndex: "100000"
			});
			document.body.prepend(this.frameBox);
		}
		this.triggerCanvas.initialize();
		this.audioBufferPromise = firstGroup.renderAudio(renderOptions.encoderOptions.alignedFromUs / 1e3, renderOptions.encoderOptions.alignedToUs / 1e3);
		this.audioBufferPromise.catch(() => {});
	}
	async beginFrame(frameNumber, isLast) {
		if (this.renderOptions === void 0) throw new Error("No renderOptions");
		const workbench = document.querySelector("ef-workbench");
		if (workbench) this.setWorkbenchRendering(true);
		const firstGroup = shallowGetTimegroups(workbench || document.body)[0];
		if (!firstGroup) throw new Error("No temporal elements found");
		const frameTime = this.renderOptions.encoderOptions.fromMs + frameNumber * this.frameDurationMs;
		const frameTimeMs = Number(Number(frameTime).toFixed(5));
		const timing = await firstGroup.seekForRender(frameTimeMs);
		this.timingFrameCount++;
		for (const key of Object.keys(this.timingAccum)) this.timingAccum[key] += timing[key];
		if (this.timingFrameCount >= 30) {
			const n = this.timingFrameCount;
			console.log(`[EF_FRAMEGEN] seekForRender phase avg (${n} frames):`, `total=${(this.timingAccum.totalMs / n).toFixed(1)}ms`, `uc1=${(this.timingAccum.updateComplete1Ms / n).toFixed(1)}ms`, `uc2=${(this.timingAccum.updateComplete2Ms / n).toFixed(1)}ms`, `uc3=${(this.timingAccum.updateComplete3Ms / n).toFixed(1)}ms`, `text=${(this.timingAccum.textSegmentsMs / n).toFixed(1)}ms`, `renderFrame=${(this.timingAccum.renderFrameMs / n).toFixed(1)}ms`, `rf.query=${(this.timingAccum.renderFrameQueryMs / n).toFixed(1)}ms`, `rf.prepare=${(this.timingAccum.renderFramePrepareMs / n).toFixed(1)}ms`, `rf.draw=${(this.timingAccum.renderFrameDrawMs / n).toFixed(1)}ms`, `rf.anims=${(this.timingAccum.renderFrameAnimsMs / n).toFixed(1)}ms`, `frameTasks=${(this.timingAccum.frameTasksMs / n).toFixed(1)}ms`);
			this.timingFrameCount = 0;
			for (const key of Object.keys(this.timingAccum)) this.timingAccum[key] = 0;
		}
		if (this.showFrameBox) this.frameBox.innerHTML = `
        <div>🖼️   Frame: ${frameNumber}</div>
        <div>🕛 Segment: ${this.time.toFixed(4)}</div>
        <div>🕛   Frame: ${firstGroup.currentTimeMs.toFixed(4)}</div>
        <div>  from-to: ${this.renderOptions.encoderOptions.fromMs.toFixed(4)} - ${this.renderOptions.encoderOptions.toMs.toFixed(4)}</div>
      `;
		this.drawVerificationStrip(frameNumber);
		if (isLast && this.audioBufferPromise) {
			const renderedAudio = await this.audioBufferPromise;
			const channelCount = renderedAudio.numberOfChannels;
			const interleavedSamples = new Float32Array(channelCount * renderedAudio.length);
			for (let i = 0; i < renderedAudio.length; i++) for (let j = 0; j < channelCount; j++) interleavedSamples.set(renderedAudio.getChannelData(j).slice(i, i + 1), i * channelCount + j);
			if (this.BRIDGE) this.BRIDGE.frameReady(frameNumber, interleavedSamples.buffer);
			else {
				const fileReader = new FileReader();
				fileReader.readAsDataURL(new Blob([interleavedSamples.buffer]));
				await new Promise((resolve, reject) => {
					fileReader.onload = resolve;
					fileReader.onerror = reject;
				});
				return fileReader.result;
			}
			this.setWorkbenchRendering(false);
		} else if (this.BRIDGE) this.BRIDGE.frameReady(frameNumber, /* @__PURE__ */ new ArrayBuffer(0));
		else {
			const fileReader = new FileReader();
			fileReader.readAsDataURL(new Blob([]));
			await new Promise((resolve, reject) => {
				fileReader.onload = resolve;
				fileReader.onerror = reject;
			});
			return fileReader.result;
		}
	}
};
if (typeof window !== "undefined") window.EF_FRAMEGEN = new EFFramegen();

//#endregion
//# sourceMappingURL=EF_FRAMEGEN.js.map