import { Color, PerspectiveCamera, PMREMGenerator } from "three";

import { getCameraController } from "../engine/engine_camera.js";
import { addNewComponent, getOrAddComponent } from "../engine/engine_components.js";
import { Context } from "../engine/engine_context.js";
import { ContextEvent, ContextRegistry } from "../engine/engine_context_registry.js";
import { NeedleEngineHTMLElement } from "../engine/engine_element.js";
import type { ICamera, IContext } from "../engine/engine_types.js";
import { getParam } from "../engine/engine_utils.js";
import { Camera, ClearFlags } from "./Camera.js";
import { OrbitControls } from "./OrbitControls.js";
import { EnvironmentScene } from "./utils/EnvironmentScene.js";

const debug = getParam("debugmissingcamera");

/**
 * Handler for missing camera events. Creates a default fallback camera when no camera is found in the scene.
 * Sets up camera properties based on the context and HTML element attributes.
 * 
 * @param evt The context event containing scene and configuration information
 * @returns The created camera component
 */
ContextRegistry.registerCallback(ContextEvent.MissingCamera, (evt) => {
    if (debug) console.warn("Creating missing camera")
    const scene = evt.context.scene;

    const cameraObject = new PerspectiveCamera();
    cameraObject.name = "Default Fallback Camera"
    scene.add(cameraObject);

    const camInstance = new Camera();
    camInstance.sourceId = evt.files?.[0]?.src ?? "unknown"
    camInstance.fieldOfView = 35;

    const transparentAttribute = evt.context.domElement.getAttribute("transparent");
    if (transparentAttribute != undefined) {
        camInstance.clearFlags = ClearFlags.Uninitialized;
    }
    // Set the clearFlags to a skybox if we have one OR if the user set a skybox image attribute
    else if (evt.context.domElement.getAttribute("skybox-image")?.length || evt.context.domElement.getAttribute("background-image")?.length || (evt.context as Context).lightmaps.tryGetSkybox(camInstance.sourceId)) {
        camInstance.clearFlags = ClearFlags.Skybox;
        // TODO: can we store the backgroundBlurriness in the gltf file somewhere except inside the camera?
        // e.g. when we export a scene from blender without a camera in the scene
        camInstance.backgroundBlurriness = .2; // same as in blender 0.5
    }
    else {
        camInstance.clearFlags = ClearFlags.SolidColor;

        // Don't set the background color if the user set a background color in the <needle-engine> element
        if (!evt.context.domElement.getAttribute("background-color")) {
            let backgroundColor = "#efefef";
            if (typeof window !== undefined && (window.matchMedia('(prefers-color-scheme: dark)').matches)) {
                backgroundColor = "#1f1f1f";
            }
            scene.background = new Color(backgroundColor); // dont set it on the camera because this might be controlled from "background-color" attribute which is set on the scene directly. If the camera has a background color, it will override the scene's background color
        }

        // Generate a default environment map if none is set
        if (!scene.environment) {
            const pmremGenerator = new PMREMGenerator(evt.context.renderer);
            const env = new EnvironmentScene("neutral");
            scene.environment = pmremGenerator.fromScene(env, .025).texture;
        }
    }


    const cam = addNewComponent(cameraObject, camInstance, true) as ICamera;
    cameraObject.position.x = 0;
    cameraObject.position.y = 1;
    cameraObject.position.z = 2;

    const engineElement = evt.context.domElement as NeedleEngineHTMLElement
    // If the camera is missing and the <needle-engine controls> is not set to false, create default camera controls
    // That way we still create controls if the attribute is not added to <needle-engine> at all
    if (engineElement?.cameraControls != false) {
        createDefaultCameraControls(evt.context, cam);
    }

    return cam;
});

/**
 * Handler for context creation events. Checks if camera controls should be added
 * to the main camera when the context is created.
 * 
 * @param evt The context creation event containing the context information
 */
ContextRegistry.registerCallback(ContextEvent.ContextCreated, (evt) => {
    if (!evt.context.mainCamera) {
        if (debug) console.log("Will not auto-fit because a default camera exists");
        return;
    }

    // check if <needle-engine camera-controls> attribute is present or enabled
    const engineElement = evt.context.domElement as NeedleEngineHTMLElement
    if (engineElement?.cameraControls == true) {

        // Check if something else already acts as a camera controller
        const existing = getCameraController(evt.context.mainCamera);
        if (existing?.isCameraController == true) {
            if (debug) console.log("Will not auto-fit because a camera controller exists");
            return;
        }
        createDefaultCameraControls(evt.context);
    }
})

/**
 * Creates default orbit camera controls for the specified camera.
 * Configures auto-rotation and auto-fit settings based on HTML attributes.
 * 
 * @param context The rendering context
 * @param cam Optional camera component to attach controls to (uses main camera if not specified)
 */
function createDefaultCameraControls(context: IContext, cam?: ICamera) {

    cam = cam ?? context.mainCameraComponent;
    const cameraObject = cam?.gameObject;
    if (debug) console.log("Creating default camera controls", cam?.name)
    if (cameraObject) {
        const orbit = getOrAddComponent(cameraObject, OrbitControls) as OrbitControls;
        orbit.sourceId = cam?.sourceId ?? "unknown";
        const autoRotate = context.domElement.getAttribute("auto-rotate");
        orbit.autoRotate = autoRotate !== undefined && autoRotate !== null && (autoRotate != "0" && autoRotate?.toLowerCase() != "false");
        orbit.autoRotateSpeed = 0.5;
        orbit.autoFit = true;
        if (orbit.autoRotate && autoRotate) {
            const autoRotateValue = parseFloat(autoRotate);
            if (!isNaN(autoRotateValue)) {
                orbit.autoRotateSpeed = autoRotateValue;
            }
        }
    }
    else {
        console.warn("Missing camera object, can not add orbit controls")
    }
}