class OutfitLayer extends HTMLElement {
	#internals;

	constructor() {
		super();
		this.#internals = this.attachInternals();
	}

	connectedCallback() {
		setTimeout(() => this.#connectToChildren(), 0);
	}

	disconnectedCallback() {
		window.removeEventListener("message", this.#onMessage);
	}

	#connectToChildren() {
		const image = this.querySelector("img");
		const iframe = this.querySelector("iframe");

		if (image) {
			image.addEventListener("load", () => this.#setStatus("loaded"));
			image.addEventListener("error", () => this.#setStatus("error"));
			this.#setStatus(image.complete ? "loaded" : "loading");
		} else if (iframe) {
			this.iframe = iframe;
			window.addEventListener("message", (m) => this.#onMessage(m));
			this.#setStatus("loading");
		} else {
			throw new Error(
				`<outfit-layer> must contain an <img> or <iframe> tag`,
			);
		}
	}

	#onMessage({ source, data }) {
		if (source !== this.iframe.contentWindow) {
			return;
		}

		if (
			data.type === "status" &&
			["loaded", "error"].includes(data.status)
		) {
			this.#setStatus(data.status);
		} else {
			throw new Error(
				`<outfit-layer> got unexpected message: ${JSON.stringify(data)}`,
			);
		}
	}

	#setStatus(newStatus) {
		this.#internals.states.clear();
		this.#internals.states.add(newStatus);
	}
}

customElements.define("outfit-layer", OutfitLayer);

// Morph turbo-frames on this page, to reuse asset nodes when we want to—very
// important for movies!—but ensure that it *doesn't* do its usual behavior of
// aggressively reusing existing <outfit-layer> nodes for entirely different
// assets. (It's a lot clearer for managing the loading state, and not showing
// old incorrect layers!) (We also tried using `id` to enforce this… no luck.)
addEventListener("turbo:before-frame-render", (event) => {
	if (typeof Idiomorph !== "undefined") {
		event.detail.render = (currentElement, newElement) => {
			Idiomorph.morph(currentElement, newElement.innerHTML, {
				morphStyle: "innerHTML",
				callbacks: {
					beforeNodeMorphed: (currentNode, newNode) => {
						// If Idiomorph wants to transform an <outfit-layer> to
						// have a different data-asset-id attribute, we replace
						// the node ourselves and abort the morph.
						if (
							newNode.tagName === "OUTFIT-LAYER" &&
							newNode.getAttribute("data-asset-id") !==
								currentNode.getAttribute("data-asset-id")
						) {
							currentNode.replaceWith(newNode);
							return false;
						}
					},
				},
			});
		};
	}
});