import { BloomEffect as _BloomEffect, EffectAttribute } from "postprocessing";
import { MathUtils } from "three";

import { MODULES } from "../../../engine/engine_modules.js";
import { serializable } from "../../../engine/engine_serialization.js";
import { PostProcessingEffect } from "../PostProcessingEffect.js";
import { VolumeParameter } from "../VolumeParameter.js";
import { registerCustomEffectType } from "../VolumeProfile.js";

/**
 * [BloomEffect](https://engine.needle.tools/docs/api/BloomEffect) can be used to make bright areas in the scene glow.  
 * @link Sample https://engine.needle.tools/samples/postprocessing
 * @example
 * ```typescript
 * const bloom = new Bloom();
 * bloom.intensity.value = 1.5;
 * bloom.threshold.value = 0.5;
 * bloom.scatter.value = 0.5;
 * volume.add(bloom);
 * ```
 * 
 * @summary Bloom Post-Processing Effect
 * @category Effects
 * @group Components
 */
export class BloomEffect extends PostProcessingEffect {

    /** Whether to use selective bloom by default */
    static useSelectiveBloom = false;

    get typeName() {
        return "Bloom";
    }

    /**
     * The bloom threshold controls at what brightness level the bloom effect will be applied.  
     * A higher value means the bloom will be applied to brighter areas or lights only
     * @default 0.9
     */
    @serializable(VolumeParameter)
    readonly threshold: VolumeParameter = new VolumeParameter(.9);

    /**
     * Intensity of the bloom effect. A higher value will increase the intensity of the bloom effect.
     * @default 1
     */
    @serializable(VolumeParameter)
    readonly intensity: VolumeParameter = new VolumeParameter(1);

    /**
     * Scatter value. The higher the value, the more the bloom will scatter.
     * @default 0.7
     */
    @serializable(VolumeParameter)
    readonly scatter: VolumeParameter = new VolumeParameter(.7);

    /**
     * Set to true to use selective bloom when the effect gets created.
     * @default false
     */
    selectiveBloom?: boolean;

    init() {
        this.threshold.valueProcessor = (v: number) => v;
        this.intensity.valueProcessor = (v: number) => v;
        this.scatter.valueProcessor = (v: number) => v;
    }

    onCreateEffect() {
        let bloom: _BloomEffect;

        if (this.selectiveBloom == undefined) {
            this.selectiveBloom = BloomEffect.useSelectiveBloom;
        }

        if (this.selectiveBloom) {
            // https://github.com/pmndrs/postprocessing/blob/64d2829f014cfec97a46bf3c109f3abc55af0715/demo/src/demos/BloomDemo.js#L265
            const selectiveBloom = bloom = new MODULES.POSTPROCESSING.MODULE.SelectiveBloomEffect(this.context.scene, this.context.mainCamera!, {
                blendFunction: MODULES.POSTPROCESSING.MODULE.BlendFunction.ADD,
                mipmapBlur: true,
                luminanceThreshold: this.threshold.value,
                luminanceSmoothing: this.scatter.value,
                radius: 0.85, // default value
                intensity: this.intensity.value,
            });
            selectiveBloom.inverted = true;
        }
        else {
            bloom = new MODULES.POSTPROCESSING.MODULE.BloomEffect({
                blendFunction: MODULES.POSTPROCESSING.MODULE.BlendFunction.ADD,
                mipmapBlur: true,
                luminanceThreshold: this.threshold.value,
                luminanceSmoothing: this.scatter.value,
                radius: 0.85, // default value
                intensity: this.intensity.value, 
            });
        }

        this.intensity.onValueChanged = newValue => {
            bloom!.intensity = newValue;
        };
        this.threshold.onValueChanged = newValue => {
            // for some reason the threshold needs to be gamma-corrected
            bloom!.luminanceMaterial.threshold = Math.pow(newValue, 2.2);
        };
        this.scatter.onValueChanged = newValue => {
            bloom!.luminancePass.enabled = true;
            bloom!.luminanceMaterial.smoothing = newValue;
            if (bloom["mipmapBlurPass"])
                // heuristic so it looks similar to "scatter" in other engines
                bloom!["mipmapBlurPass"].radius = MathUtils.lerp(0.1, 0.9, newValue);
        };

        return bloom;
    }

}
registerCustomEffectType("Bloom", BloomEffect);