var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { CompressedCubeTexture, CubeTexture, CubeUVReflectionMapping, EquirectangularRefractionMapping } from "three";
import { isDevEnvironment } from "../engine/debug/debug.js";
import { ContextEvent, ContextRegistry } from "../engine/engine_context_registry.js";
import { syncField } from "../engine/engine_networking_auto.js";
import { loadPMREM } from "../engine/engine_pmrem.js";
import { serializable } from "../engine/engine_serialization_decorator.js";
import { addAttributeChangeCallback, getParam, removeAttributeChangeCallback, toSourceId } from "../engine/engine_utils.js";
import { registerObservableAttribute } from "../engine/webcomponents/needle-engine.extras.js";
import { Camera } from "./Camera.js";
import { Behaviour, GameObject } from "./Component.js";
const debug = getParam("debugskybox");
registerObservableAttribute("background-image");
registerObservableAttribute("environment-image");
const MagicSkyboxNames = {
    "studio": {
        url: "https://cdn.needle.tools/static/skybox/modelviewer-Neutral.pmrem4x4.ktx2?pmrem",
        url_low: "https://cdn.needle.tools/static/skybox/modelviewer-Neutral-small.pmrem4x4.ktx2?pmrem"
    },
    "blurred-skybox": {
        url: "https://cdn.needle.tools/static/skybox/blurred-skybox.pmrem4x4.ktx2?pmrem",
        url_low: "https://cdn.needle.tools/static/skybox/blurred-skybox-small.pmrem4x4.ktx2?pmrem"
    },
    "quicklook-ar": {
        url: "https://cdn.needle.tools/static/skybox/QuickLook-ARMode.pmrem4x4.ktx2?pmrem",
        url_low: "https://cdn.needle.tools/static/skybox/QuickLook-ARMode-small.pmrem4x4.ktx2?pmrem"
    },
    "quicklook": {
        url: "https://cdn.needle.tools/static/skybox/QuickLook-ObjectMode.pmrem4x4.ktx2?pmrem",
        url_low: "https://cdn.needle.tools/static/skybox/QuickLook-ObjectMode-small.pmrem4x4.ktx2?pmrem"
    }
};
function createRemoteSkyboxComponent(context, url, skybox, environment, attribute) {
    // when the user sets the attribute to a color we can not handle it as a skybox url.
    if (url === "transparent" || url?.startsWith("rgb") || url?.startsWith("#")) {
        console.warn(`Needle Engine: Invalid ${attribute} value (${url}). Did you mean to set background-color instead?`);
        return null;
    }
    const remote = new RemoteSkybox();
    remote.sourceId = toSourceId(url);
    remote.allowDrop = false;
    remote.allowNetworking = false;
    remote.background = skybox;
    remote.environment = environment;
    GameObject.addComponent(context.scene, remote);
    const urlChanged = newValue => {
        if (debug)
            console.log(attribute, "CHANGED TO", newValue);
        if (newValue) {
            if (typeof newValue !== "string") {
                console.warn("Invalid attribute value for " + attribute);
                return;
            }
            remote.setSkybox(newValue);
        }
        else {
            if (remote.sourceId) {
                if (environment) {
                    if (!context.sceneLighting.internalEnableReflection(remote.sourceId)) {
                        context.scene.environment = null;
                    }
                }
                if (skybox) {
                    const skybox = context.lightmaps.tryGetSkybox(remote.sourceId);
                    context.scene.background = skybox;
                }
            }
        }
    };
    addAttributeChangeCallback(context.domElement, attribute, urlChanged);
    remote.addEventListener("destroy", () => {
        if (debug)
            console.log("Destroyed attribute remote skybox", attribute);
        removeAttributeChangeCallback(context.domElement, attribute, urlChanged);
    });
    return remote.setSkybox(url);
}
const promises = new Array();
ContextRegistry.registerCallback(ContextEvent.ContextCreationStart, (args) => {
    const context = args.context;
    const backgroundImage = context.domElement.getAttribute("background-image");
    const environmentImage = context.domElement.getAttribute("environment-image");
    if (backgroundImage) {
        if (debug)
            console.log("Creating RemoteSkybox to load background " + backgroundImage);
        // if the user is loading a GLB without a camera then the CameraUtils (which creates the default camera)
        // checks if we have this attribute set and then sets the skybox clearflags accordingly
        // if the user has a GLB with a camera but set to solid color then the skybox image is not visible -> we will just warn then and not override the camera settings
        const promise = createRemoteSkyboxComponent(context, backgroundImage, true, false, "background-image");
        if (promise)
            promises.push(promise);
    }
    if (environmentImage) {
        if (debug)
            console.log("Creating RemoteSkybox to load environment " + environmentImage);
        const promise = createRemoteSkyboxComponent(context, environmentImage, false, true, "environment-image");
        if (promise)
            promises.push(promise);
    }
});
ContextRegistry.registerCallback(ContextEvent.ContextCreationStart, () => {
    return Promise.all(promises).finally(() => {
        promises.length = 0;
    });
});
/**
 * The [RemoteSkybox](https://engine.needle.tools/docs/api/RemoteSkybox) component allows you to set the skybox or environment texture of a scene from a URL, a local file or a static skybox name.
 * It supports .hdr, .exr, .jpg, .png, and .ktx2 files.
 *
 * **HTML Attributes:**
 * You can control skybox and environment from HTML using `<needle-engine>` attributes:
 * - `background-image`: Sets the scene background/skybox image
 * - `environment-image`: Sets the scene environment map (for reflections and lighting)
 *
 * These attributes accept URLs or magic skybox names (see examples below).
 *
 * **Magic Skybox Names:**
 * Built-in optimized skyboxes hosted on Needle CDN:
 * - `"studio"` - Neutral studio lighting (default)
 * - `"blurred-skybox"` - Blurred environment
 * - `"quicklook"` - Apple QuickLook object mode style
 * - `"quicklook-ar"` - Apple QuickLook AR mode style
 *
 * ### Events
 * - `dropped-unknown-url`: Emitted when a file is dropped on the scene. The event detail contains the sender, the url and a function to apply the url.
 *
 * @example Using HTML attributes
 * ```html
 * <needle-engine
 *   background-image="https://example.com/skybox.hdr"
 *   environment-image="studio">
 * </needle-engine>
 * ```
 *
 * @example Using magic skybox names
 * ```html
 * <needle-engine background-image="studio"></needle-engine>
 * <needle-engine environment-image="quicklook"></needle-engine>
 * ```
 *
 * @example Adding via code
 * ```ts
 * GameObject.addComponent(gameObject, RemoteSkybox, {
 *   url: "https://example.com/skybox.hdr",
 *   background: true,
 *   environment: true
 * });
 * ```
 *
 * @example Handle custom dropped URL
 * ```ts
 * const skybox = GameObject.addComponent(gameObject, RemoteSkybox);
 * skybox.addEventListener("dropped-unknown-url", (evt) => {
 *    let url = evt.detail.url;
 *    console.log("User dropped file", url);
 *    // change url or resolve it differently
 *    url = "https://example.com/skybox.hdr";
 *    // apply the url
 *    evt.detail.apply(url);
 * });
 * ```
 *
 * @example Update skybox at runtime
 * ```ts
 * skybox.setSkybox("https://example.com/skybox.hdr");
 * // Or use a magic name:
 * skybox.setSkybox("studio");
 * ```
 *
 * @summary Sets the skybox or environment texture of a scene
 * @category Rendering
 * @group Components
 * @see {@link Camera} for clearFlags and background control
 * @link https://engine.needle.tools/docs/html.html#needle-engine-element
 */
export class RemoteSkybox extends Behaviour {
    /**
     * URL to a remote skybox.
     * To update the skybox/environment map use `setSkybox(url)`.
     *
     * The url can also be set to a magic skybox name.
     * Magic name options are: "quicklook", "quicklook-ar", "studio", "blurred-skybox".
     * These will resolve to built-in skyboxes hosted on the Needle CDN that are static, optimized for the web and will never change.
     *
     * @example
     * ```ts
     * skybox.url = "https://example.com/skybox.hdr";
     * ```
     */
    url = "studio";
    /**
     * When enabled a user can drop a link to a skybox image on the scene to set the skybox.
     * @default true
     */
    allowDrop = true;
    /**
     * When enabled the skybox will be set as the background of the scene.
     * @default true
     */
    background = true;
    /**
     * When enabled the skybox will be set as the environment of the scene (to be used as environment map for reflections and lighting)
     * @default true
     */
    environment = true;
    /**
     * When enabled dropped skybox urls (or assigned skybox urls) will be networked to other users in the same networked room.
     * @default true
     */
    allowNetworking = true;
    _prevUrl;
    _prevLoadedEnvironment;
    _prevEnvironment = null;
    _prevBackground = null;
    /** @internal */
    onEnable() {
        this.setSkybox(this.url);
        this.registerDropEvents();
    }
    /** @internal */
    onDisable() {
        if (this.context.scene.environment === this._prevLoadedEnvironment) {
            this.context.scene.environment = this._prevEnvironment;
            if (!Camera.backgroundShouldBeTransparent(this.context))
                this.context.scene.background = this._prevBackground;
            this._prevLoadedEnvironment = undefined;
        }
        this.unregisterDropEvents();
        // Re-apply the skybox/background settings of the main camera
        this.context.mainCameraComponent?.applyClearFlags();
    }
    urlChangedSyncField() {
        if (this.allowNetworking && this.url) {
            // omit local dragged files from being handled
            if (this.isRemoteTexture(this.url)) {
                this.setSkybox(this.url);
            }
            else if (debug) {
                console.warn(`RemoteSkybox: Not setting skybox: ${this.url} is not a remote texture. If you want to set a local texture, set allowNetworking to false.`);
            }
        }
    }
    /**
     * Set the skybox from a given url
     * @param url The url of the skybox image
     * @param name Define name of the file with extension if it isn't apart of the url
     * @returns Whether the skybox was successfully set
     */
    async setSkybox(url, name) {
        if (!this.activeAndEnabled)
            return false;
        url = tryParseMagicSkyboxName(url, this.environment, this.background);
        if (!url)
            return false;
        name ??= url;
        if (!this.isValidTextureType(name)) {
            console.warn("Potentially invalid skybox URL: \"" + name + "\" on " + (this.name || this.gameObject?.name || "context"));
        }
        if (debug)
            console.log("Set RemoteSkybox url: " + url);
        if (this._prevUrl === url && this._prevLoadedEnvironment) {
            this.apply();
            return true;
        }
        else {
            this._prevLoadedEnvironment?.dispose();
            this._prevLoadedEnvironment = undefined;
        }
        this._prevUrl = url;
        const texture = await loadPMREM(url, this.context.renderer);
        if (!texture) {
            if (debug)
                console.warn("RemoteSkybox: Failed to load texture from url", url);
            return false;
        }
        // Check if we're not disabled or destroyed
        if (!this.enabled || this.destroyed) {
            if (debug)
                console.warn("RemoteSkybox: Component is disabled or destroyed");
            return false;
        }
        // Check if the url has changed while loading
        if (this._prevUrl !== url) {
            if (debug)
                console.warn("RemoteSkybox: URL changed while loading texture, aborting setSkybox");
            return false; // URL changed while loading
        }
        // Update the current url
        this.url = url;
        this._prevLoadedEnvironment = texture;
        this.apply();
        return true;
    }
    apply() {
        const envMap = this._prevLoadedEnvironment;
        if (!envMap)
            return;
        if ((envMap instanceof CubeTexture || envMap instanceof CompressedCubeTexture) || envMap.mapping == CubeUVReflectionMapping) {
            // Nothing to do
        }
        else {
            envMap.mapping = EquirectangularRefractionMapping;
            envMap.needsUpdate = true;
        }
        if (this.destroyed)
            return;
        if (!this.context) {
            console.warn("RemoteSkybox: Context is not available - can not apply skybox.");
            return;
        }
        // capture state
        if (this.context.scene.background !== envMap)
            this._prevBackground = this.context.scene.background;
        if (this.context.scene.environment !== envMap)
            this._prevEnvironment = this.context.scene.environment;
        if (debug)
            console.log("Set RemoteSkybox (" + ((this.environment && this.background) ? "environment and background" : this.environment ? "environment" : this.background ? "background" : "none") + ")", this.url, !Camera.backgroundShouldBeTransparent(this.context));
        if (this.environment)
            this.context.scene.environment = envMap;
        if (this.background && !Camera.backgroundShouldBeTransparent(this.context))
            this.context.scene.background = envMap;
        if (this.context.mainCameraComponent?.backgroundBlurriness !== undefined)
            this.context.scene.backgroundBlurriness = this.context.mainCameraComponent.backgroundBlurriness;
    }
    validProtocols = ["file:", "blob:", "data:"];
    validTextureTypes = [".ktx2", ".hdr", ".exr", ".jpg", ".jpeg", ".png"];
    isRemoteTexture(url) {
        return url.startsWith("http://") || url.startsWith("https://");
    }
    isValidTextureType(url) {
        for (const type of this.validTextureTypes) {
            if (url.includes(type))
                return true;
        }
        for (const protocol of this.validProtocols) {
            if (url.startsWith(protocol))
                return true;
        }
        return false;
    }
    registerDropEvents() {
        this.unregisterDropEvents();
        this.context.domElement.addEventListener("dragover", this.onDragOverEvent);
        this.context.domElement.addEventListener("drop", this.onDrop);
    }
    unregisterDropEvents() {
        this.context.domElement.removeEventListener("dragover", this.onDragOverEvent);
        this.context.domElement.removeEventListener("drop", this.onDrop);
    }
    onDragOverEvent = (e) => {
        if (!this.allowDrop)
            return;
        if (!e.dataTransfer)
            return;
        for (const type of e.dataTransfer.types) {
            // in ondragover we dont get access to the content
            // but if we have a uri list we can assume
            // someone is maybe dragging a image file
            // so we want to capture this
            if (type === "text/uri-list" || type === "Files") {
                e.preventDefault();
            }
        }
    };
    onDrop = (e) => {
        if (!this.allowDrop)
            return;
        if (!e.dataTransfer)
            return;
        for (const type of e.dataTransfer.types) {
            if (debug)
                console.log(type);
            if (type === "text/uri-list") {
                const url = e.dataTransfer.getData(type);
                if (debug)
                    console.log(type, url);
                let name = new RegExp(/polyhaven.com\/asset_img\/.+?\/(?<name>.+)\.png/).exec(url)?.groups?.name;
                if (!name) {
                    name = new RegExp(/polyhaven\.com\/a\/(?<name>.+)/).exec(url)?.groups?.name;
                }
                if (debug)
                    console.log(name);
                if (name) {
                    const skyboxurl = "https://dl.polyhaven.org/file/ph-assets/HDRIs/exr/1k/" + name + "_1k.exr";
                    console.log(`[Remote Skybox] Setting skybox from url: ${skyboxurl}`);
                    e.preventDefault();
                    this.setSkybox(skyboxurl);
                    break;
                }
                else if (this.isValidTextureType(url)) {
                    console.log("[Remote Skybox] Setting skybox from url: " + url);
                    e.preventDefault();
                    this.setSkybox(url);
                    break;
                }
                else {
                    console.warn(`[RemoteSkybox] Unknown url ${url}. If you want to load a skybox from a url, make sure it is a valid image url. Url must end with${this.validTextureTypes.join(", ")}.`);
                    // emit custom event - users can listen to this event and handle the url themselves
                    const evt = new CustomEvent("dropped-unknown-url", {
                        detail: {
                            sender: this,
                            event: e,
                            url,
                            apply: (url) => {
                                e.preventDefault();
                                this.setSkybox(url);
                            }
                        }
                    });
                    this.dispatchEvent(evt);
                }
            }
            else if (type == "Files") {
                const file = e.dataTransfer.files.item(0);
                if (debug)
                    console.log(type, file);
                if (!file)
                    continue;
                if (!this.isValidTextureType(file.name)) {
                    console.warn(`[RemoteSkybox]: File \"${file.name}\" is not supported. Supported files are ${this.validTextureTypes.join(", ")}`);
                    return;
                }
                // if (tryGetPreviouslyLoadedTexture(file.name) === null) {
                //     const blob = new Blob([file]);
                //     const url = URL.createObjectURL(blob);
                //     e.preventDefault();
                //     this.setSkybox(url, file.name);
                // }
                // else 
                {
                    e.preventDefault();
                    this.setSkybox(file.name);
                }
                break;
            }
        }
    };
}
__decorate([
    syncField(RemoteSkybox.prototype.urlChangedSyncField),
    serializable(URL)
], RemoteSkybox.prototype, "url", void 0);
__decorate([
    serializable()
], RemoteSkybox.prototype, "allowDrop", void 0);
__decorate([
    serializable()
], RemoteSkybox.prototype, "background", void 0);
__decorate([
    serializable()
], RemoteSkybox.prototype, "environment", void 0);
__decorate([
    serializable()
], RemoteSkybox.prototype, "allowNetworking", void 0);
function tryParseMagicSkyboxName(str, environment, background) {
    if (str === null || str === undefined)
        return null;
    const useLowRes = environment && !background;
    const value = MagicSkyboxNames[str.toLowerCase()];
    if (value) {
        return useLowRes ? value.url_low : value.url;
    }
    else if (typeof str === "string" && str?.length && (isDevEnvironment() || debug)) {
        // Only warn if the string looks like it was meant to be a magic skybox name.
        // Strings that contain "/" or "." are paths or URLs, not magic names.
        const looksLikePath = str.includes("/") || str.includes(".");
        if (!looksLikePath) {
            console.warn(`RemoteSkybox: Unknown magic skybox name "${str}". Valid names are: ${Object.keys(MagicSkyboxNames).map(n => `"${n}"`).join(", ")}`);
        }
    }
    return str;
}
//# sourceMappingURL=Skybox.js.map