import { AssetReference, type ProgressCallback } from "../engine/engine_addressables.js";
import { InstantiateOptions } from "../engine/engine_gameobject.js";
import { InstantiateIdProvider } from "../engine/engine_networking_instantiate.js";
import { serializable } from "../engine/engine_serialization_decorator.js";
import { getParam } from "../engine/engine_utils.js";
import { Behaviour } from "../engine-components/Component.js";

const debug = getParam("debugnestedgltf");

/** The nested gltf is a component that is used to load a gltf file when the component becomes active (start)  
 * It will load the gltf file and instantiate it as a child of the parent of the GameObject that has this component
 */
export class NestedGltf extends Behaviour {
    /**
     * A reference to the gltf file that should be loaded
     */
    @serializable(AssetReference)
    filePath?: AssetReference;

    /**
     * EXPERIMENTAL for cloud asset loading
     */
    loadAssetInParent = true;

    private _isLoadingOrDoneLoading: boolean = false;

    /** Register a callback that will be called when the progress of the loading changes */
    listenToProgress(evt: ProgressCallback) {
        this.filePath?.beginListenDownload(evt)
    }

    /** Begin loading the referenced gltf file in filePath */
    preload() {
        this.filePath?.preload();
    }

    /** @internal */
    async start() {
        if (this._isLoadingOrDoneLoading) return;
        if (debug) console.log(this, this.guid);

        const parent = this.gameObject.parent;
        if (parent) {
            this._isLoadingOrDoneLoading = true;
            const opts = new InstantiateOptions();
            // we need to provide stable guids for creating nested gltfs
            opts.idProvider = new InstantiateIdProvider(this.hash(this.guid));
            opts.parent = this.loadAssetInParent !== false ? parent : this.gameObject;
            this.gameObject.updateMatrix();
            const matrix = this.gameObject.matrix;
            if (debug) console.log("Load nested:", this.filePath?.url ?? this.filePath, this.gameObject.position);
            const res = await this.filePath?.instantiate?.call(this.filePath, opts);
            if (debug) console.log("Nested loaded:", this.filePath?.url ?? this.filePath, res);
            if (res && this.loadAssetInParent !== false) {
                res.matrixAutoUpdate = false;
                res.matrix.identity();
                res.applyMatrix4(matrix);
                res.matrixAutoUpdate = true;
                res.layers.disableAll();
                res.layers.set(this.layer);

                this.dispatchEvent(new CustomEvent("loaded", { detail: { instance: res, assetReference: this.filePath } }));
            }
            if (debug) console.log("Nested loading done:", this.filePath?.url ?? this.filePath, res);
        }
    }

    /** @internal */
    onDestroy(): void {
        // When the NestedGLTF gets destroyed we assume the loaded glTF is also destroyed
        // meaning the resources, textures etc are disposed
        // When this NestedGLTF would now be loaded again the AssetReference would still be in the loaded state
        // so it would instantiate without textures etc...
        // Perhaps we want to add a dispose callback or event method too?
        // Somehow we have to clean the AssetReference state
        this.filePath?.unload();
    }


    private hash(str: string): number {
        let hash = 0;
        for (let i = 0; i < str.length; i++) {
            hash = str.charCodeAt(i) + ((hash << 5) - hash);
        }
        return hash;
    }
}