import { quantizeToFrameTimeMs } from "../../../utils/frameTime.js";
import { TWMixin } from "../../TWMixin2.js";
import { __decorate } from "../../../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
import { TargetController } from "../../../elements/TargetController.js";
import { EFVideo } from "../../../elements/EFVideo.js";
import { timelineStateContext } from "../timelineStateContext.js";
import { ThumbnailExtractor } from "../../../elements/EFMedia/shared/ThumbnailExtractor.js";
import { generateThumbnailsFromClone } from "../../../preview/renderTimegroupToCanvas.js";
import { previewSettingsContext } from "../../previewSettingsContext.js";
import { EFTimegroup } from "../../../elements/EFTimegroup.js";
import { consume } from "@lit/context";
import { LitElement, css, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { createRef, ref } from "lit/directives/ref.js";

//#region src/gui/timeline/tracks/EFThumbnailStrip.ts
/** Padding for virtual rendering */
const VIRTUAL_RENDER_PADDING_PX = 100;
/**
* Mutable queue for timestamp generation.
* Allows updating timestamps while generator is consuming them.
*/
var MutableTimestampQueue = class {
	#timestamps = [];
	/** Replace entire queue with new timestamps (sorted) */
	reset(timestamps) {
		this.#timestamps = [...timestamps].sort((a, b) => a - b);
	}
	/** Keep only these specific timestamps (maintains order) */
	retainOnly(timestamps) {
		const keep = new Set(timestamps);
		this.#timestamps = this.#timestamps.filter((t) => keep.has(t));
	}
	/** Append timestamps to end (sorted) */
	append(timestamps) {
		this.#timestamps.push(...[...timestamps].sort((a, b) => a - b));
	}
	/** Get next timestamp (removes from front) */
	shift() {
		return this.#timestamps.shift();
	}
	/** Get remaining timestamps without modifying queue */
	remaining() {
		return [...this.#timestamps];
	}
	/** Check if queue is empty */
	isEmpty() {
		return this.#timestamps.length === 0;
	}
};
let EFThumbnailStrip = class EFThumbnailStrip$1 extends TWMixin(LitElement) {
	constructor(..._args) {
		super(..._args);
		this.target = "";
		this.targetElement = null;
		this.thumbnailHeight = 24;
		this.thumbnailSpacingPx = 48;
		this.pixelsPerMs = null;
		this.useIntrinsicDuration = false;
		this.thumbnailDimensions = {
			width: 0,
			height: 0
		};
		this._isLoadingThumbnails = false;
	}
	static {
		this.styles = [css`
      :host {
        display: block;
        position: relative;
        width: 100%;
        height: 100%;
        overflow: hidden;
      }

      .thumbnail-container {
        position: relative;
        width: 100%;
        height: 100%;
        overflow: hidden;
      }

      .error-message {
        display: flex;
        align-items: center;
        justify-content: center;
        width: 100%;
        height: 100%;
        padding: 8px;
        color: var(--ef-color-error);
        font-size: 12px;
        background: color-mix(in srgb, var(--ef-color-error) 10%, transparent);
      }

      canvas {
        position: absolute;
        image-rendering: pixelated;
        image-rendering: crisp-edges;
      }

      .shimmer-overlay {
        display: none;
        position: absolute;
        inset: 0;
        background: linear-gradient(
          90deg,
          color-mix(in srgb, var(--ef-color-text, #fafafa) 12%, transparent) 0%,
          color-mix(in srgb, var(--ef-color-text, #fafafa) 28%, transparent) 50%,
          color-mix(in srgb, var(--ef-color-text, #fafafa) 12%, transparent) 100%
        );
        background-size: 200% 100%;
        pointer-events: none;
      }

      .shimmer-overlay.active {
        display: block;
        animation: shimmer-strip var(--ef-loading-shimmer-duration, 1.5s) linear infinite;
      }

      @keyframes shimmer-strip {
        0% { background-position: 200% 0; }
        100% { background-position: -200% 0; }
      }
    `];
	}
	#targetController;
	#abortController = null;
	#renderRequested = false;
	#canvasContainer = createRef();
	#lastRequiredTimestamps = "";
	#thumbnailCache = /* @__PURE__ */ new Map();
	#timegroupQueue = new MutableTimestampQueue();
	#timegroupClone = null;
	#timegroupGenerator = null;
	#timegroupGeneratorAbort = null;
	#previewContainer = null;
	#updateInProgress = false;
	#consumerRunning = false;
	#pendingTimestamps = /* @__PURE__ */ new Set();
	#retryScheduled = false;
	#thumbnailPhase = 0;
	#previousPixelsPerMs = null;
	#targetReadyStateHandler = null;
	#targetContentChangeHandler = null;
	#resizeObserver = null;
	#hostWidth = 0;
	/**
	* Check if target is valid (EFVideo or root EFTimegroup)
	*/
	get isValidTarget() {
		const el = this.targetElement;
		if (!el) return false;
		if (el instanceof EFVideo) return true;
		if (el instanceof EFTimegroup) return el.isRootTimegroup === true;
		return false;
	}
	get #timelineState() {
		return this.timelineState;
	}
	get #thumbnailDimensions() {
		return this.thumbnailDimensions;
	}
	get #effectiveDurationMs() {
		const element = this.targetElement;
		if (!element) return 0;
		if (this.useIntrinsicDuration) return element.intrinsicDurationMs ?? element.durationMs ?? 0;
		return element.durationMs ?? 0;
	}
	get #effectivePixelsPerMs() {
		if (this.#timelineState?.pixelsPerMs != null) return this.#timelineState.pixelsPerMs;
		if (this.pixelsPerMs != null) return this.pixelsPerMs;
		const durationMs = this.#effectiveDurationMs;
		if (this.#hostWidth > 0 && durationMs > 0) return this.#hostWidth / durationMs;
		return .04;
	}
	connectedCallback() {
		super.connectedCallback();
		if (this.target && !this.targetElement) this.#targetController = new TargetController(this);
		this.#resizeObserver = new ResizeObserver((entries) => {
			const entry = entries[0];
			if (!entry) return;
			const width = entry.contentRect.width;
			if (width !== this.#hostWidth) {
				this.#hostWidth = width;
				this.requestUpdate();
				this.#scheduleRender();
			}
		});
		this.#resizeObserver.observe(this);
	}
	disconnectedCallback() {
		super.disconnectedCallback();
		this.#abortController?.abort();
		this.#cleanupTimegroupGenerator();
		this.#detachTargetListeners(this.targetElement);
		this.#resizeObserver?.disconnect();
		this.#resizeObserver = null;
	}
	willUpdate(changedProperties) {
		super.willUpdate(changedProperties);
		if (changedProperties.has("target")) {
			if (this.target && !this.targetElement && !this.#targetController) this.#targetController = new TargetController(this);
		}
		if (changedProperties.has("targetElement") || changedProperties.has("thumbnailHeight")) this.thumbnailDimensions = this.#calculateThumbnailDimensions();
		if (changedProperties.has("targetElement")) {
			const oldTarget = changedProperties.get("targetElement");
			this.#detachTargetListeners(oldTarget);
			this.#attachTargetListeners(this.targetElement);
		}
	}
	#attachTargetListeners(target) {
		if (!target) return;
		this.#targetReadyStateHandler = () => {
			this.requestUpdate();
			this.#scheduleRender();
		};
		this.#targetContentChangeHandler = () => {
			this.requestUpdate();
			this.#scheduleRender();
		};
		target.addEventListener("readystatechange", this.#targetReadyStateHandler);
		target.addEventListener("contentchange", this.#targetContentChangeHandler);
		if (target.contentReadyState === "ready") {
			this.requestUpdate();
			this.#scheduleRender();
		}
	}
	#detachTargetListeners(target) {
		if (!target) return;
		if (this.#targetReadyStateHandler) {
			target.removeEventListener("readystatechange", this.#targetReadyStateHandler);
			this.#targetReadyStateHandler = null;
		}
		if (this.#targetContentChangeHandler) {
			target.removeEventListener("contentchange", this.#targetContentChangeHandler);
			this.#targetContentChangeHandler = null;
		}
	}
	updated(changedProperties) {
		super.updated(changedProperties);
		if (changedProperties.has("targetElement") || changedProperties.has("thumbnailSpacingPx") || changedProperties.has("pixelsPerMs") || changedProperties.has("thumbnailHeight") || changedProperties.has("timelineState")) this.#scheduleRender();
	}
	/**
	* Calculate thumbnail dimensions from target element's actual bounds
	*/
	#calculateThumbnailDimensions() {
		const el = this.targetElement;
		if (!el) return {
			width: 0,
			height: 0
		};
		const bounds = el.getBoundingClientRect();
		if (bounds.width === 0 || bounds.height === 0) return {
			width: this.thumbnailHeight * (16 / 9),
			height: this.thumbnailHeight
		};
		const aspectRatio = bounds.width / bounds.height;
		return {
			width: Math.round(this.thumbnailHeight * aspectRatio),
			height: this.thumbnailHeight
		};
	}
	/**
	* Calculate visible thumbnails based on viewport
	*/
	#calculateVisibleThumbnails() {
		if (!this.isValidTarget) return [];
		const element = this.targetElement;
		if (!element) return [];
		const scrollLeft = this.#timelineState?.viewportScrollLeft ?? 0;
		const viewportWidth = this.#timelineState?.viewportWidth ?? (this.#hostWidth || 800);
		const pixelsPerMs = this.#effectivePixelsPerMs;
		const durationMs = this.#effectiveDurationMs;
		if (durationMs === 0) return [];
		const trackWidthPx = durationMs * pixelsPerMs;
		const fps = element.fps ?? 30;
		const visibleStartPx = scrollLeft - VIRTUAL_RENDER_PADDING_PX;
		const visibleEndPx = scrollLeft + viewportWidth + VIRTUAL_RENDER_PADDING_PX;
		const thumbnails = [];
		const { width, height } = this.#thumbnailDimensions;
		const gapPx = parseFloat(getComputedStyle(this).getPropertyValue("--ef-thumbnail-gap")) || 2;
		const thumbnailStride = Math.max(this.thumbnailSpacingPx, width + gapPx);
		const isZoom = this.#previousPixelsPerMs !== null && this.#previousPixelsPerMs !== pixelsPerMs;
		if (this.#previousPixelsPerMs === null) this.#thumbnailPhase = 0;
		else if (isZoom) this.#thumbnailPhase = scrollLeft % thumbnailStride;
		else if (scrollLeft < thumbnailStride) this.#thumbnailPhase = 0;
		this.#previousPixelsPerMs = pixelsPerMs;
		const startIndex = Math.max(0, Math.floor((visibleStartPx - this.#thumbnailPhase) / thumbnailStride));
		const endIndex = Math.ceil((visibleEndPx - this.#thumbnailPhase) / thumbnailStride);
		for (let i = startIndex; i <= endIndex; i++) {
			const thumbX = this.#thumbnailPhase + i * thumbnailStride;
			if (thumbX >= 0 && thumbX < trackWidthPx) {
				const timeMs = quantizeToFrameTimeMs(thumbX / pixelsPerMs, fps);
				if (timeMs >= 0 && timeMs < durationMs) thumbnails.push({
					timeMs,
					x: thumbX,
					width,
					height
				});
			}
		}
		return thumbnails;
	}
	/**
	* Schedule thumbnail render on next frame
	*/
	#scheduleRender() {
		if (this.#renderRequested) return;
		this.#renderRequested = true;
		requestAnimationFrame(() => {
			this.#renderRequested = false;
			this.#renderThumbnails();
		});
	}
	/**
	* Render thumbnails with cancellation support
	*/
	async #renderThumbnails() {
		this.#abortController?.abort();
		this.#abortController = new AbortController();
		const signal = this.#abortController.signal;
		const visibleThumbnails = this.#calculateVisibleThumbnails();
		if (visibleThumbnails.length === 0) {
			this.#clearCanvas();
			return;
		}
		const requiredTimestamps = visibleThumbnails.map((t) => t.timeMs);
		const timestampsString = requiredTimestamps.join(", ");
		if (timestampsString !== this.#lastRequiredTimestamps) {
			this.#lastRequiredTimestamps = timestampsString;
			if (this.targetElement instanceof EFVideo) this.#updateVideoCapture(requiredTimestamps, signal).catch((error) => {
				if (error instanceof DOMException && error.name === "AbortError") return;
				console.error("Thumbnail capture error:", error);
			});
			else if (this.targetElement instanceof EFTimegroup) this.#updateTimegroupCapture(requiredTimestamps);
		}
		if (signal.aborted) return;
		const maxNeighborDistanceMs = 3e3;
		const results = visibleThumbnails.map((t) => {
			let canvas = this.#thumbnailCache.get(t.timeMs);
			if (!canvas) {
				let nearestTimeMs = null;
				let minDistance = Infinity;
				for (const cachedTimeMs of this.#thumbnailCache.keys()) {
					const distance = Math.abs(cachedTimeMs - t.timeMs);
					if (distance < minDistance && distance <= maxNeighborDistanceMs) {
						minDistance = distance;
						nearestTimeMs = cachedTimeMs;
					}
				}
				if (nearestTimeMs !== null) canvas = this.#thumbnailCache.get(nearestTimeMs);
			}
			return { canvas: canvas ?? null };
		});
		this.#drawThumbnails(visibleThumbnails, results);
		const hasEmptySlots = results.some((r) => r.canvas === null);
		if (this._isLoadingThumbnails !== hasEmptySlots) this._isLoadingThumbnails = hasEmptySlots;
	}
	/**
	* Update video thumbnail capture
	*/
	async #updateVideoCapture(timestamps, signal) {
		const video = this.targetElement;
		if (!video) return;
		const uncached = timestamps.filter((t) => !this.#thumbnailCache.has(t));
		if (uncached.length === 0) return;
		const mediaEngineTask = video.mediaEngineTask;
		if (!mediaEngineTask) return;
		const mediaEngine = await mediaEngineTask.taskComplete;
		if (!mediaEngine) return;
		const sourceTimestamps = uncached.map((t) => this.#getSourceTimeMs(t));
		const extractor = new ThumbnailExtractor(mediaEngine);
		const videoTrack = mediaEngine.tracks.video ?? mediaEngine.tracks.scrub;
		if (!videoTrack) return;
		const results = await extractor.extractThumbnails(sourceTimestamps, videoTrack, video.durationMs ?? 0, signal);
		for (let i = 0; i < uncached.length; i++) {
			const thumbnail = results[i]?.thumbnail;
			const timestamp = uncached[i];
			if (thumbnail && timestamp !== void 0) this.#thumbnailCache.set(timestamp, thumbnail);
		}
		this.#scheduleRender();
	}
	/**
	* Update timegroup thumbnail capture using mutable queue
	*/
	async #updateTimegroupCapture(timestamps) {
		const timegroup = this.targetElement;
		if (!timegroup) return;
		const uncached = timestamps.filter((t) => !this.#thumbnailCache.has(t)).sort((a, b) => a - b);
		if (uncached.length === 0) return;
		if (this.#updateInProgress) {
			this.#pendingTimestamps.clear();
			uncached.forEach((t) => this.#pendingTimestamps.add(t));
			if (!this.#retryScheduled) {
				this.#retryScheduled = true;
				requestAnimationFrame(() => {
					this.#retryScheduled = false;
					if (this.#pendingTimestamps.size > 0) {
						const pending = Array.from(this.#pendingTimestamps);
						this.#pendingTimestamps.clear();
						this.#updateTimegroupCapture(pending);
					}
				});
			}
			return;
		}
		this.#updateInProgress = true;
		try {
			if (this.#timegroupGenerator) {
				this.#timegroupGeneratorAbort?.abort();
				this.#timegroupGeneratorAbort = new AbortController();
				this.#timegroupQueue.reset(uncached);
			} else if (this.#timegroupClone) {
				this.#timegroupQueue.reset(uncached);
				this.#timegroupGeneratorAbort = new AbortController();
				this.#timegroupGenerator = generateThumbnailsFromClone(this.#timegroupClone.clone, this.#previewContainer, this.#timegroupQueue, {
					scale: .25,
					contentReadyMode: "blocking",
					blockingTimeoutMs: 1e3,
					signal: this.#timegroupGeneratorAbort.signal
				});
				await this.#consumeTimegroupGenerator();
			} else await this.#startTimegroupGenerator(timegroup, uncached);
		} finally {
			this.#updateInProgress = false;
			if (this.#pendingTimestamps.size > 0 && !this.#retryScheduled) {
				this.#retryScheduled = true;
				requestAnimationFrame(() => {
					this.#retryScheduled = false;
					if (this.#pendingTimestamps.size > 0) {
						const pending = Array.from(this.#pendingTimestamps);
						this.#pendingTimestamps.clear();
						this.#updateTimegroupCapture(pending);
					}
				});
			}
		}
	}
	/**
	* Start timegroup thumbnail generator
	*/
	async #startTimegroupGenerator(timegroup, timestamps) {
		this.#timegroupClone = await timegroup.createRenderClone();
		this.#previewContainer = this.#timegroupClone.container;
		await this.#timegroupClone.clone.updateComplete;
		const litElements = this.#previewContainer.querySelectorAll("*");
		const updatePromises = [];
		for (const el of litElements) if ("updateComplete" in el) updatePromises.push(el.updateComplete);
		await Promise.all(updatePromises);
		const textSegments = this.#previewContainer.querySelectorAll("ef-text-segment");
		const textUpdatePromises = [];
		for (const seg of textSegments) if ("updateComplete" in seg) textUpdatePromises.push(seg.updateComplete);
		await Promise.all(textUpdatePromises);
		await new Promise((resolve) => requestAnimationFrame(resolve));
		if (!this.#timegroupClone) return;
		if (timestamps.length > 0) await this.#timegroupClone.clone.seekForRender(timestamps[0]);
		await document.fonts.ready;
		const images = this.#previewContainer.querySelectorAll("img");
		const imagePromises = [];
		for (const img of images) if (!img.complete) imagePromises.push(new Promise((resolve, _reject) => {
			img.onload = () => resolve();
			img.onerror = () => resolve();
			setTimeout(() => resolve(), 5e3);
		}));
		await Promise.all(imagePromises);
		if (!this.#timegroupClone) return;
		this.#timegroupQueue.reset(timestamps);
		this.#timegroupGeneratorAbort = new AbortController();
		this.#timegroupGenerator = generateThumbnailsFromClone(this.#timegroupClone.clone, this.#previewContainer, this.#timegroupQueue, {
			scale: .25,
			contentReadyMode: "blocking",
			blockingTimeoutMs: 1e3,
			signal: this.#timegroupGeneratorAbort.signal
		});
		await this.#consumeTimegroupGenerator();
	}
	/**
	* Consume generator and handle cleanup
	*/
	async #consumeTimegroupGenerator() {
		if (this.#consumerRunning) return;
		this.#consumerRunning = true;
		if (!this.#timegroupGenerator) {
			this.#consumerRunning = false;
			return;
		}
		try {
			for await (const { timeMs, canvas } of this.#timegroupGenerator) {
				this.#thumbnailCache.set(timeMs, canvas);
				this.#scheduleRender();
			}
		} catch (err) {
			console.warn("Timegroup thumbnail generation error:", err);
		} finally {
			this.#timegroupGenerator = null;
			this.#consumerRunning = false;
		}
	}
	/**
	* Cleanup timegroup generator and clone
	*/
	#cleanupTimegroupGenerator() {
		this.#timegroupGeneratorAbort?.abort();
		this.#timegroupGeneratorAbort = null;
		this.#timegroupGenerator = null;
		if (this.#previewContainer) {
			this.#previewContainer.remove();
			this.#previewContainer = null;
		}
		if (this.#timegroupClone) {
			this.#timegroupClone.cleanup();
			this.#timegroupClone = null;
		}
	}
	/**
	* Clear all canvas elements
	*/
	#clearCanvas() {
		const container = this.#canvasContainer.value;
		if (container) container.innerHTML = "";
	}
	/**
	* Translate composition time to source time for videos (handles trim)
	*/
	#getSourceTimeMs(compositionTimeMs) {
		if (this.useIntrinsicDuration) return compositionTimeMs;
		const el = this.targetElement;
		if (el instanceof EFVideo) return compositionTimeMs + (el.sourceStartMs ?? 0);
		return compositionTimeMs;
	}
	/**
	* Draw thumbnails to canvas elements
	*/
	#drawThumbnails(thumbnails, results) {
		const container = this.#canvasContainer.value;
		if (!container) return;
		container.innerHTML = "";
		for (let i = 0; i < thumbnails.length; i++) {
			const thumbnail = thumbnails[i];
			const result = results[i];
			if (!thumbnail) continue;
			const canvas = document.createElement("canvas");
			canvas.width = thumbnail.width;
			canvas.height = thumbnail.height;
			canvas.style.left = `${thumbnail.x}px`;
			canvas.style.top = "0";
			canvas.style.width = `${thumbnail.width}px`;
			canvas.style.height = `${thumbnail.height}px`;
			const ctx = canvas.getContext("2d");
			if (!ctx) continue;
			if (result?.canvas) {
				ctx.drawImage(result.canvas, 0, 0, thumbnail.width, thumbnail.height);
				if (this.previewSettings?.showThumbnailTimestamps) {
					ctx.fillStyle = "rgba(0, 0, 0, 0.8)";
					ctx.fillRect(2, 2, 95, 16);
					ctx.fillStyle = "yellow";
					ctx.font = "11px monospace";
					ctx.textAlign = "left";
					ctx.textBaseline = "top";
					ctx.fillText(`${Math.round(thumbnail.timeMs)}ms`, 5, 4);
				}
			} else {
				ctx.fillStyle = getComputedStyle(this).getPropertyValue("--ef-color-bg-inset").trim() || "rgba(100, 100, 100, 0.3)";
				ctx.fillRect(0, 0, thumbnail.width, thumbnail.height);
				ctx.strokeStyle = getComputedStyle(this).getPropertyValue("--ef-color-border-subtle").trim() || "rgba(150, 150, 150, 0.5)";
				ctx.lineWidth = 1;
				ctx.strokeRect(0, 0, thumbnail.width, thumbnail.height);
				ctx.fillStyle = "white";
				ctx.font = "10px monospace";
				ctx.textAlign = "center";
				ctx.textBaseline = "middle";
				ctx.fillText(`${Math.round(thumbnail.timeMs)}ms`, thumbnail.width / 2, thumbnail.height / 2);
			}
			container.appendChild(canvas);
		}
	}
	render() {
		if (!this.target && !this.targetElement) return html`<div class="error-message">No target specified</div>`;
		if (this.target && !this.targetElement) return html`<div class="error-message">
        Target element "${this.target}" not found
      </div>`;
		if (!this.isValidTarget) return html`<div class="error-message">
        Invalid target: "${this.targetElement.tagName?.toLowerCase() || "unknown"}" must be ef-video or root ef-timegroup
      </div>`;
		return html`
      <div
        class="thumbnail-container"
        style="max-width: ${this.#effectiveDurationMs * this.#effectivePixelsPerMs}px;"
        ${ref(this.#canvasContainer)}
      ></div>
      <div
        class="shimmer-overlay ${this._isLoadingThumbnails ? "active" : ""}"
      ></div>
    `;
	}
};
__decorate([property({ type: String })], EFThumbnailStrip.prototype, "target", void 0);
__decorate([property({ attribute: false })], EFThumbnailStrip.prototype, "targetElement", void 0);
__decorate([property({
	type: Number,
	attribute: "thumbnail-height"
})], EFThumbnailStrip.prototype, "thumbnailHeight", void 0);
__decorate([property({
	type: Number,
	attribute: "thumbnail-spacing-px"
})], EFThumbnailStrip.prototype, "thumbnailSpacingPx", void 0);
__decorate([property({
	type: Number,
	attribute: "pixels-per-ms"
})], EFThumbnailStrip.prototype, "pixelsPerMs", void 0);
__decorate([property({
	type: Boolean,
	attribute: "use-intrinsic-duration"
})], EFThumbnailStrip.prototype, "useIntrinsicDuration", void 0);
__decorate([consume({
	context: timelineStateContext,
	subscribe: true
}), state()], EFThumbnailStrip.prototype, "timelineState", void 0);
__decorate([consume({
	context: previewSettingsContext,
	subscribe: true
}), state()], EFThumbnailStrip.prototype, "previewSettings", void 0);
__decorate([state()], EFThumbnailStrip.prototype, "thumbnailDimensions", void 0);
__decorate([state()], EFThumbnailStrip.prototype, "_isLoadingThumbnails", void 0);
EFThumbnailStrip = __decorate([customElement("ef-thumbnail-strip")], EFThumbnailStrip);

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