import { PRIORITY_IMAGE } from "../preview/FrameController.js";
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
import { EFTemporal } from "./EFTemporal.js";
import { EFSourceMixin } from "./EFSourceMixin.js";
import { FetchMixin } from "./FetchMixin.js";
import { LitElement, css, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { createRef, ref } from "lit/directives/ref.js";

//#region src/elements/EFImage.ts
let EFImage = class EFImage$1 extends EFTemporal(EFSourceMixin(FetchMixin(LitElement), { assetType: "image_files" })) {
	constructor(..._args) {
		super(..._args);
		this.imageRef = createRef();
		this.canvasRef = createRef();
	}
	static {
		this.styles = [css`
      :host {
        display: block;
        position: relative;
        object-fit: contain;
        object-position: center;
      }
      canvas, img {
        width: 100%;
        height: 100%;
        object-fit: inherit;
        object-position: inherit;
      }
    `];
	}
	static get observedAttributes() {
		return [...super.observedAttributes || [], "asset-id"];
	}
	attributeChangedCallback(name, oldValue, newValue) {
		if (name === "asset-id") {
			this.fileId = newValue;
			return;
		}
		super.attributeChangedCallback(name, oldValue, newValue);
	}
	/**
	* Render version counter - increments when visual content changes.
	* Used by RenderContext to cache rendered dataURLs.
	*/
	#renderVersion = 0;
	/**
	* Get the current render version.
	* Version increments when src or fileId changes.
	* @public
	*/
	get renderVersion() {
		return this.#renderVersion;
	}
	/**
	* Whether the loaded image has an alpha channel.
	* JPEG images don't have alpha, PNG/WebP may have alpha.
	*/
	#hasAlpha = true;
	/**
	* Get whether the image has an alpha channel.
	* Used to determine if we should encode as PNG (alpha) or JPEG (no alpha).
	* @public
	*/
	get hasAlpha() {
		return this.#hasAlpha;
	}
	#fileId = null;
	set fileId(value) {
		this.#fileId = value;
	}
	get fileId() {
		return this.#fileId ?? this.getAttribute("file-id") ?? this.getAttribute("asset-id");
	}
	/** @deprecated Use fileId instead */
	get assetId() {
		return this.fileId;
	}
	set assetId(value) {
		this.fileId = value;
	}
	render() {
		const assetPath = this.assetPath();
		return this.isDirectUrl(assetPath) ? html`<img ${ref(this.imageRef)} src=${assetPath} />` : html`<canvas ${ref(this.canvasRef)}></canvas>`;
	}
	isDirectUrl(src) {
		if (this.fileId) return false;
		return src.startsWith("data:");
	}
	assetPath() {
		if (this.fileId) return `${this.apiHost}/api/v1/files/${this.fileId}`;
		if (this.isDirectUrl(this.src)) return this.src;
		const normalizedSrc = this.src.startsWith("/") ? this.src.replace(/^\/+/, "") : this.src;
		return `/api/v1/assets/image?src=${encodeURIComponent(normalizedSrc)}`;
	}
	get hasOwnDuration() {
		return this.hasExplicitDuration;
	}
	#imageLoaded = false;
	#imageLoadPromise = null;
	#lastLoadedPath = null;
	#currentObjectUrl = null;
	shouldAutoReady() {
		return !this.src && !this.fileId;
	}
	/**
	* Load image from the configured source
	*/
	async loadImage(signal) {
		const assetPath = this.assetPath();
		if (!this.src && !this.fileId) return;
		if (this.#imageLoaded && this.#lastLoadedPath === assetPath) {
			this.setContentReadyState("ready");
			return;
		}
		if (this.#imageLoadPromise && this.#lastLoadedPath === assetPath) return this.#imageLoadPromise;
		this.setContentReadyState("loading");
		if (this.isDirectUrl(assetPath)) {
			this.#lastLoadedPath = assetPath;
			this.#imageLoadPromise = this.#waitForImageElement(signal);
			try {
				await this.#imageLoadPromise;
				this.#imageLoaded = true;
				this.setContentReadyState("ready");
			} catch (error) {
				if (error instanceof DOMException && error.name === "AbortError") throw error;
				console.error("EFImage img element load error", error);
				this.setContentReadyState("error");
				throw error;
			} finally {
				this.#imageLoadPromise = null;
			}
			return;
		}
		this.#lastLoadedPath = assetPath;
		this.#imageLoadPromise = this.#doLoadImage(assetPath, signal);
		try {
			await this.#imageLoadPromise;
			this.#imageLoaded = true;
			this.setContentReadyState("ready");
		} catch (error) {
			if (error instanceof DOMException && error.name === "AbortError") throw error;
			if (error instanceof Error && error.message === "Canvas not ready") return;
			console.error("EFImage load error", error);
			this.setContentReadyState("error");
		} finally {
			this.#imageLoadPromise = null;
		}
	}
	async #waitForImageElement(signal) {
		if (!this.imageRef.value) throw new Error("Image element not ready");
		const img = this.imageRef.value;
		if (img.complete && img.naturalHeight !== 0) return;
		return new Promise((resolve, reject) => {
			if (signal?.aborted) {
				reject(new DOMException("Aborted", "AbortError"));
				return;
			}
			const abortHandler = () => {
				reject(new DOMException("Aborted", "AbortError"));
			};
			signal?.addEventListener("abort", abortHandler, { once: true });
			img.onload = () => {
				signal?.removeEventListener("abort", abortHandler);
				resolve();
			};
			img.onerror = (error) => {
				signal?.removeEventListener("abort", abortHandler);
				reject(error);
			};
		});
	}
	async #doLoadImage(assetPath, signal) {
		const response = await this.fetch(assetPath, { signal });
		signal?.throwIfAborted();
		const image = new Image();
		const blob = await response.blob();
		signal?.throwIfAborted();
		const mimeType = blob.type.toLowerCase();
		this.#hasAlpha = !mimeType.includes("jpeg") && !mimeType.includes("jpg");
		image.src = URL.createObjectURL(blob);
		await new Promise((resolve, reject) => {
			if (signal?.aborted) {
				URL.revokeObjectURL(image.src);
				reject(new DOMException("Aborted", "AbortError"));
				return;
			}
			const abortHandler = () => {
				URL.revokeObjectURL(image.src);
				reject(new DOMException("Aborted", "AbortError"));
			};
			signal?.addEventListener("abort", abortHandler, { once: true });
			image.onload = () => {
				signal?.removeEventListener("abort", abortHandler);
				resolve();
			};
			image.onerror = (error) => {
				signal?.removeEventListener("abort", abortHandler);
				URL.revokeObjectURL(image.src);
				reject(error);
			};
		});
		signal?.throwIfAborted();
		if (!this.canvasRef.value) throw new Error("Canvas not ready");
		const ctx = this.canvasRef.value.getContext("2d", { willReadFrequently: true });
		if (!ctx) throw new Error("Canvas 2d context not ready");
		let canvasWidth = image.width || image.naturalWidth;
		let canvasHeight = image.height || image.naturalHeight;
		if (canvasWidth === 0 || canvasHeight === 0) {
			const computedStyle = getComputedStyle(this);
			const elementWidth = parseFloat(computedStyle.width);
			const elementHeight = parseFloat(computedStyle.height);
			if (elementWidth > 0 && elementHeight > 0) {
				canvasWidth = elementWidth;
				canvasHeight = elementHeight;
			} else {
				canvasWidth = 300;
				canvasHeight = 150;
			}
		}
		this.canvasRef.value.width = canvasWidth;
		this.canvasRef.value.height = canvasHeight;
		try {
			await image.decode();
		} catch (decodeError) {}
		ctx.clearRect(0, 0, canvasWidth, canvasHeight);
		try {
			ctx.drawImage(image, 0, 0, canvasWidth, canvasHeight);
		} catch (drawError) {
			console.error(`[EFImage] drawImage failed:`, drawError);
			throw drawError;
		}
		if (this.#currentObjectUrl && this.#currentObjectUrl !== image.src) URL.revokeObjectURL(this.#currentObjectUrl);
		this.#currentObjectUrl = image.src;
	}
	/**
	* Query readiness state for a given time.
	* @implements FrameRenderable
	*/
	getFrameState(_timeMs) {
		return {
			needsPreparation: !this.#imageLoaded,
			isReady: this.#imageLoaded,
			priority: PRIORITY_IMAGE
		};
	}
	/**
	* Async preparation - waits for image to load.
	* @implements FrameRenderable
	*/
	async prepareFrame(_timeMs, signal) {
		await this.loadImage(signal);
		signal.throwIfAborted();
	}
	/**
	* Synchronous render - image is already displayed via img element or canvas.
	* @implements FrameRenderable
	*/
	renderFrame(_timeMs) {}
	updated(changedProperties) {
		super.updated(changedProperties);
		if (changedProperties.has("src") || changedProperties.has("fileId")) {
			this.#imageLoaded = false;
			if (changedProperties.get("src") !== void 0 || changedProperties.get("fileId") !== void 0) this.emitContentChange("source");
			this.loadImage().catch(() => {});
			this.#renderVersion++;
		}
	}
	disconnectedCallback() {
		super.disconnectedCallback();
		if (this.#currentObjectUrl) {
			URL.revokeObjectURL(this.#currentObjectUrl);
			this.#currentObjectUrl = null;
		}
	}
	/**
	* Get the natural dimensions of the image.
	* Returns null if the image hasn't loaded yet.
	*
	* @public
	*/
	getNaturalDimensions() {
		const img = this.imageRef.value;
		if (img && img.naturalWidth > 0 && img.naturalHeight > 0) return {
			width: img.naturalWidth,
			height: img.naturalHeight
		};
		const canvas = this.canvasRef.value;
		if (canvas && canvas.width > 0 && canvas.height > 0) return {
			width: canvas.width,
			height: canvas.height
		};
		return null;
	}
};
__decorate([property({
	type: String,
	attribute: "file-id",
	reflect: true
})], EFImage.prototype, "fileId", null);
EFImage = __decorate([customElement("ef-image")], EFImage);

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