import { Engine } from '@babylonjs/core/Engines/engine';
import { Scene } from '@babylonjs/core/scene';
import { AdomCamera } from './Camera/AdomCamera';
import type { Nullable } from '@babylonjs/core/types';
import type { ShadowGenerator, ISceneLoaderAsyncResult } from '@babylonjs/core';
import type { SpotLight } from '@babylonjs/core/Lights/spotLight';
import type { HemisphericLight } from '@babylonjs/core/Lights/hemisphericLight';
import { RenderTargetTexture } from '@babylonjs/core';

export class AdomStatics {
    public static engine: Nullable<Engine> = null;
    public static scene: Nullable<Scene> = null;
    public static camera: Nullable<AdomCamera> = null;
    public static shadowGenerator: Nullable<ShadowGenerator> = null;
    public static spotLight: Nullable<SpotLight> = null;
    /** Bottom-fill hemispheric light — illuminates the underside of parts.
     *  Default OFF (intensity 0). Toggleable via ThreeDViewer.setBottomLight(). */
    public static bottomLight: Nullable<HemisphericLight> = null;

    static modelLoadingPromises = new Map<string, Promise<ISceneLoaderAsyncResult>>();

    private static _windowResizeHandler: Nullable<EventListener> = null;

    /**
     * Initialize the engine and scene.
     * @param canvas - The canvas element to render to
     * @param zUp - If true, use Z-up coordinate system (default). If false, use Y-up (Babylon default).
     */
    public static async init(canvas: HTMLCanvasElement, zUp: boolean = true): Promise<void> {
        if (this.engine && this.scene && this.camera) {
            return;
        }

        this.engine = new Engine(canvas, true, { useHighPrecisionFloats: true });
        this.scene = new Scene(this.engine);
        this.scene.useRightHandedSystem = zUp;

        this.camera = new AdomCamera(this.scene, canvas, zUp);
        this.camera.disableBounds();
        this.camera.setClickToSetRotationPoint(false);
        this.scene.activeCamera = this.camera;
    }

    public static dispose(): void {
        if (this.scene) {
            this.scene.dispose();
            this.scene = null;
        }
        if (this.engine) {
            this.engine.stopRenderLoop();
            this.engine.dispose();
            this.engine = null;
        }
        if (this.camera) {
            this.camera.dispose();
            this.camera = null;
        }
        if (this.shadowGenerator) {
            this.shadowGenerator.dispose();
            this.shadowGenerator = null;
        }
        if (this.spotLight) {
            this.spotLight.dispose();
            this.spotLight = null;
        }
        if (this.bottomLight) {
            this.bottomLight.dispose();
            this.bottomLight = null;
        }
        if (this.modelLoadingPromises) {
            this.modelLoadingPromises.clear();
        }
    }

    /**
     * Re-renders the shadow map once. Defers by one frame so that any light
     * frustum or caster-list changes from the current frame are applied first.
     */
    private static _pcssTransparencyWarned = false;

    /**
     * PCSS combined with soft transparent shadows renders NO shadows at all,
     * silently (pixel-diff proven: PCSS 0.0% change, PCF 26.7%). Auto-correct
     * to PCF so consumers flipping useContactHardeningShadow on a scene with
     * enableSoftTransparentShadow don't lose shadows. Called from
     * refreshShadows and once per frame (two boolean reads, negligible).
     */
    public static enforceShadowFilterCompat(): void {
        const sg = this.shadowGenerator;
        if (!sg) return;
        if (sg.useContactHardeningShadow && sg.enableSoftTransparentShadow) {
            if (!this._pcssTransparencyWarned) {
                this._pcssTransparencyWarned = true;
                console.warn(
                    '[adom-3d-viewer] PCSS (useContactHardeningShadow) + enableSoftTransparentShadow ' +
                    'silently renders no shadows. Falling back to PCF (usePercentageCloserFiltering). ' +
                    'Use PCF whenever soft transparent shadows are enabled.'
                );
            }
            sg.useContactHardeningShadow = false;
            sg.usePercentageCloserFiltering = true;
        }
    }

    public static refreshShadows(): void {
        if (!this.shadowGenerator || !this.scene) return;
        const sg = this.shadowGenerator;
        const shadowMap = sg.getShadowMap();
        if (!shadowMap) return;

        this.enforceShadowFilterCompat();

        // Animated models (glTF animationGroups, e.g. a press-fit insertion or a
        // spin) need the shadow map re-rendered EVERY frame so the cast shadows
        // track the moving meshes. A one-shot render freezes the shadow at frame 0,
        // which reads as broken (the pin moves, its shadow does not). Keep the
        // cheap render-once only for fully static models.
        const hasAnimation = (this.scene.animationGroups || []).length > 0;
        if (hasAnimation) {
            shadowMap.refreshRate = RenderTargetTexture.REFRESHRATE_RENDER_ONEVERYFRAME;
            return;
        }

        this.scene.onAfterRenderObservable.addOnce(() => {
            shadowMap.refreshRate = RenderTargetTexture.REFRESHRATE_RENDER_ONCE;
            shadowMap.resetRefreshCounter();
        });
    }
}