import { CubeReflectionMapping, CubeUVReflectionMapping, LinearSRGBColorSpace, SRGBColorSpace, Texture, TextureLoader } from "three";
import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader.js";
import { type GLTF, type GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
import { KTX2Loader } from "three/examples/jsm/loaders/KTX2Loader.js";
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";

import { isDevEnvironment } from "../debug/index.js";
import { type ILightDataRegistry } from "../engine_lightdata.js";
import { type SourceIdentifier } from "../engine_types.js";
import { getParam, PromiseAllWithErrors, resolveUrl } from "../engine_utils.js";
import { resolveReferences } from "./extension_utils.js";
import { NEEDLE_pmrem } from "./NEEDLE_pmrem.js";

// the lightmap extension is aimed to also export export skyboxes and custom reflection maps
// should we rename it?
// should we split it into multiple extensions?

export const EXTENSION_NAME = "NEEDLE_lightmaps";
const debug = getParam("debuglightmapsextension") || getParam("debuglightmaps")

export enum LightmapType {
    Lightmap = 0,
    Skybox = 1,
    Reflection = 2,
}

declare type LightmapExtension = {
    textures: Array<LightmapInfo>;
}

declare type LightmapInfo = {
    type: LightmapType,
    pointer?: string,
    index?: number;
}

export class NEEDLE_lightmaps implements GLTFLoaderPlugin {

    get name(): string {
        return EXTENSION_NAME;
    }

    private parser: GLTFParser;
    private registry: ILightDataRegistry;
    private source: SourceIdentifier;

    constructor(parser: GLTFParser, reg: ILightDataRegistry, source: SourceIdentifier) {
        this.parser = parser;
        this.registry = reg;
        this.source = source;
    }

    afterRoot(_result: GLTF): Promise<void> | null {

        const extensions = this.parser.json.extensions;
        if (extensions) {
            const ext: LightmapExtension = extensions[EXTENSION_NAME];
            if (ext) {
                const arr = ext.textures;
                if (!arr?.length) {
                    return null;
                }
                if (debug)
                    console.log(ext);

                return new Promise(async (resolve, _reject) => {

                    const dependencies: Array<Promise<any>> = [];
                    for (const entry of arr) {
                        if (entry.pointer) {
                            if (debug)
                                console.log(entry);
                            let res: Promise<any> | null = null;
                            // Check if the pointer is a json pointer:
                            if (entry.pointer.startsWith("/textures/") || /** legacy support e.g. SOC */entry.pointer.startsWith("textures/")) {
                                if (debug) console.log("Load texture from gltf", entry.pointer);
                                res = resolveReferences(this.parser, entry.pointer).then(res => this.resolveTexture(entry, res));
                            }
                            // The pointer can be a string to a file on disc
                            else if (typeof entry.pointer === "string") {
                                if (debug) console.log("Load texture from path", entry.pointer);

                                const path = resolveUrl(this.source, entry.pointer);
                                const isPMREM = path.endsWith(".pmrem.ktx2");

                                let loader: TextureLoader | EXRLoader | RGBELoader | KTX2Loader;
                                if (path.endsWith(".exr"))
                                    loader = new EXRLoader(this.parser.options.manager);
                                else if (path.endsWith(".hdr"))
                                    loader = new RGBELoader(this.parser.options.manager);
                                else if (isPMREM)
                                    loader = (this.parser.options as any).ktx2Loader; // We re-use the KTX2Loader configured on the GLTFLoader, which has GPU support detection built in
                                else
                                    loader = new TextureLoader(this.parser.options.manager);
                                res = loader.loadAsync(path, undefined).then(res => {
                                    if (isPMREM && res) {
                                        NEEDLE_pmrem.postprocess(res);
                                    }
                                    return this.resolveTexture(entry, res);
                                });
                            }
                            else if (entry.pointer === undefined) {
                                // data is missing?
                            }
                            if (res) dependencies.push(res);
                        }
                    }
                    const results = await PromiseAllWithErrors(dependencies);
                    if (results?.anyFailed) {
                        if (isDevEnvironment() || debug)
                            console.error("[NEEDLE_lightmaps]Error during extension loading:", results);
                    }
                    resolve();
                });
            }
        }
        return null;
    }

    private resolveTexture(entry: LightmapInfo, res: any) {
        const tex: Texture = res as unknown as Texture;
        if (debug) console.log("Light Texture loaded:", tex);
        if (tex?.isTexture) {
            if (!this.registry)
                console.log(LightmapType[entry.type], entry.pointer, tex);
            else {
                tex.colorSpace = LinearSRGBColorSpace;
                this.registry.registerTexture(this.source, entry.type, tex, entry.index);
            }
        }
    }
}