import type { N8AOPostPass } from "n8ao";
import { Color, DepthFormat, DepthStencilFormat, DepthTexture, PerspectiveCamera, UnsignedInt248Type, UnsignedIntType, WebGLRenderTarget } from "three";

import { MODULES } from "../../../engine/engine_modules.js";
import { serializable } from "../../../engine/engine_serialization.js";
import { validate } from "../../../engine/engine_util_decorator.js";
import { getParam } from "../../../engine/engine_utils.js";
import { type EffectProviderResult, PostProcessingEffect } from "../PostProcessingEffect.js";
import { VolumeParameter } from "../VolumeParameter.js";
import { registerCustomEffectType } from "../VolumeProfile.js";

const debugN8AO = getParam("debugN8AO");

// https://github.com/N8python/n8ao

/**See (N8AO documentation)[https://github.com/N8python/n8ao] */
export enum ScreenSpaceAmbientOcclusionN8QualityMode {
    Performance = 0,
    Low = 1,
    Medium = 2,
    High = 3,
    Ultra = 4,
}

/** Screen Space Ambient Occlusion (SSAO) effect.  
 * @category Effects
 * @group Components
 * @link [N8AO documentation](https://github.com/N8python/n8ao)
 */
export class ScreenSpaceAmbientOcclusionN8 extends PostProcessingEffect {

    get typeName() {
        return "ScreenSpaceAmbientOcclusionN8";
    }

    get pass(): N8AOPostPass {
        return this._ssao;
    }

    @validate()
    @serializable()
    gammaCorrection: boolean = true;

    /** The most important parameter for your ambient occlusion effect. 
     * Controls the radius/size of the ambient occlusion in world units. 
     * Should be set to how far you want the occlusion to extend from a given object. 
     * Set it too low, and AO becomes an edge detector. 
     * Too high, and the AO becomes "soft" and might not highlight the details you want. 
     * The radius should be one or two magnitudes less than scene scale: 
     * if your scene is 10 units across, the radius should be between 0.1 and 1. If its 100, 1 to 10.  
     * @default 1
     */
    @serializable(VolumeParameter)
    aoRadius: VolumeParameter = new VolumeParameter(1);

    /** The second most important parameter for your ambient occlusion effect. 
     * Controls how fast the ambient occlusion fades away with distance in proportion to its radius. 
     * Defaults to 1, and behind-the-scenes, is a calculated as a ratio of your radius (0.2 * distanceFalloff is the size used for attenuation). 
     * Decreasing it reduces "haloing" artifacts and improves the accuracy of your occlusion, 
     * but making it too small makes the ambient occlusion disappear entirely.
     * @default 1
     */
    @serializable(VolumeParameter)
    falloff: VolumeParameter = new VolumeParameter(1);

    /** A purely artistic control for the intensity of the AO - runs the ao through the function pow(ao, intensity), 
     * which has the effect of darkening areas with more ambient occlusion. 
     * Useful to make the effect more pronounced. 
     * An intensity of 2 generally produces soft ambient occlusion that isn't too noticeable, 
     * whereas one of 5 produces heavily prominent ambient occlusion.
     * @default 1
     */
    @serializable(VolumeParameter)
    intensity: VolumeParameter = new VolumeParameter(1);

    /** The color of the ambient occlusion. By default, it is black, but it can be changed to any color 
     * to offer a crude approximation of global illumination. 
     * Recommended in scenes where bounced light has a uniform "color", 
     * for instance a scene that is predominantly lit by a blue sky. 
     * The color is expected to be in the sRGB color space, and is automatically converted to linear space for you. 
     * Keep the color pretty dark for sensible results.
     * @default new Color(0, 0, 0)
     */
    @serializable(VolumeParameter)
    color: VolumeParameter = new VolumeParameter(new Color(0, 0, 0));

    /** If you want the AO to calculate the radius based on screen space, you can do so by setting configuration.screenSpaceRadius to true. 
     * This is useful for scenes where the camera is moving across different scales a lot, 
     * or for scenes where the camera is very close to the objects.
     * @default false
     */
    @validate()
    @serializable()
    screenspaceRadius: boolean = false;

    /**
     * The quality of the ambient occlusion effect.
     * @default ScreenSpaceAmbientOcclusionN8QualityMode.Medium
     */
    @validate()
    @serializable()
    quality: ScreenSpaceAmbientOcclusionN8QualityMode = ScreenSpaceAmbientOcclusionN8QualityMode.Medium;

    private _ssao?: N8AOPostPass;

    onValidate(): void {
        if (this._ssao) {
            this._ssao.setQualityMode(ScreenSpaceAmbientOcclusionN8QualityMode[this.quality]);
            this._ssao.configuration.gammaCorrection = this.gammaCorrection;
            this._ssao.configuration.screenSpaceRadius = this.screenspaceRadius;

        }
    }

    onCreateEffect(): EffectProviderResult {

        const cam = this.context.mainCamera! as PerspectiveCamera;

        const width = this.context.domWidth;
        const height = this.context.domHeight;

        const ssao = this._ssao = new MODULES.POSTPROCESSING_AO.MODULE.N8AOPostPass(
            this.context.scene,
            cam,
            width, height
        ) as N8AOPostPass;

        const mode = ScreenSpaceAmbientOcclusionN8QualityMode[this.quality];
        ssao.setQualityMode(mode);

        ssao.configuration.transparencyAware = false;
        // ssao.needsSwap = false;
        

        const renderTarget = new WebGLRenderTarget(width, height);
        ssao.configuration.beautyRenderTarget = renderTarget;
        ssao.configuration.autoRenderBeauty = false;
        // // If you just want a depth buffer
        // renderTarget.depthTexture = new DepthTexture(width, height, UnsignedIntType);
        // renderTarget.depthTexture.format = DepthFormat;
        // // If you want a depth buffer and a stencil buffer
        // renderTarget.depthTexture = new DepthTexture(width, height, UnsignedInt248Type);
        // renderTarget.depthTexture.format = DepthStencilFormat;


        ssao.configuration.gammaCorrection = this.gammaCorrection;
        ssao.configuration.screenSpaceRadius = this.screenspaceRadius;


        if (debugN8AO) {
            // ssao.debug = debugN8AO;
            ssao.enableDebugMode();
            console.log(ssao);
            setInterval(() => {
                console.log("SSAO", ssao.lastTime);
            }, 1000);
            setInterval(() => {
                // ssao.enabled = !ssao.enabled;
                console.log("SSAO", ssao.enabled, { ssao, autoRenderBeauty: ssao.configuration.autoRenderBeauty });
            }, 4000)
        }


        this.intensity.onValueChanged = newValue => {
            ssao.configuration.intensity = newValue;
        }
        this.falloff.onValueChanged = newValue => {
            ssao.configuration.distanceFalloff = newValue;
        }
        this.aoRadius.onValueChanged = newValue => {
            ssao.configuration.aoRadius = newValue;
        }
        this.color.onValueChanged = newValue => {
            if (!ssao.color) ssao.color = new Color();
            ssao.configuration.color.copy(newValue);
        }
        

        // const normalPass = new MODULES.POSTPROCESSING.MODULE.NormalPass(this.context.scene, cam);
        // const depthDownsamplingPass = new MODULES.POSTPROCESSING.MODULE.DepthDownsamplingPass({
        //     normalBuffer: normalPass.texture,
        //     resolutionScale: .1
        // });
        // const arr = new Array();
        // arr.push(normalPass);
        // arr.push(depthDownsamplingPass);
        // arr.push(ssao);
        return ssao;
    }

}
registerCustomEffectType("ScreenSpaceAmbientOcclusionN8", ScreenSpaceAmbientOcclusionN8);