import { EffectAttribute } from "postprocessing";
import { Uniform } from "three";

import { MODULES } from "../../../engine/engine_modules.js";
import { serializable } from "../../../engine/engine_serialization.js";
import { PostProcessingEffect } from "../PostProcessingEffect.js";
import { PostProcessingEffectOrder } from "../utils.js";
// import type { createSharpeningEffectType } from "./Sharpening.effect";

// let __SHARPENING_MODULE: typeof import("./Sharpening.effect");
// /** @hidden */
// export function internal_SetSharpeningEffectModule(module: typeof import("./Sharpening.effect")) {
//     __SHARPENING_MODULE = module;
// }

/**
 * [SharpeningEffect](https://engine.needle.tools/docs/api/SharpeningEffect) Sharpening effect enhances the details and edges in the rendered scene by increasing the contrast between adjacent pixels.
 * This effect can make textures and fine details appear clearer and more defined, improving the overall visual quality of the scene.  
 * It is particularly useful in scenes where details may be lost due to blurriness or low resolution.
 * @summary Sharpening Post-Processing Effect
 * @category Effects
 * @group Components
 */
export class SharpeningEffect extends PostProcessingEffect {

    get typeName() {
        return "Sharpening";
    }

    order: number | undefined = PostProcessingEffectOrder.Sharpening;

    private _effect?: any;

    onCreateEffect() {
        this._effect ??= new (createSharpeningEffectType())();
        return this.effect;
    }

    private get effect() {
        return this._effect;
    }

    @serializable()
    set amount(value: number) {
        this._amount = value;
        if (this._effect)
            this._effect.uniforms.get("amount")!.value = value;
    }
    get amount() {
        if (this._effect)
            return this._effect.uniforms.get("amount")!.value;
        return this._amount;
    }
    private _amount = 1;

    @serializable()
    set radius(value: number) {
        this._radius = value;
        if (this._effect)
            this._effect.uniforms.get("radius")!.value = value;
    }
    get radius() {
        if (this._effect)
            return this._effect.uniforms.get("radius")!.value;
        return this._radius;
    }
    private _radius = 1;

    // @serializable()
    // set threshold(value: number) {
    //     this.effect.uniforms.get("threshold")!.value = value;
    // }
    // get threshold() {
    //     return this.effect.uniforms.get("threshold")!.value;
    // }

}



function createSharpeningEffectType() {

    const vert = `
      void mainSupport() {
        vUv = uv;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
      }
    `

    const frag = `
    uniform sampler2D tDiffuse;
    uniform float amount;
    uniform float radius;
    
    void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) {
        float tx = 1.0 / resolution.x;
        float ty = 1.0 / resolution.y;
        vec2 texelSize = vec2(tx, ty);
    
        vec4 blurred = vec4(0.0);
        float total = 0.0;
    
        for (float x = -radius; x <= radius; x++) {
            for (float y = -radius; y <= radius; y++) {
                vec2 offset = vec2(x, y) * texelSize;
                vec4 diffuse = texture2D(tDiffuse, uv + offset);
                float weight = exp(-length(offset) * amount);
                blurred += diffuse * weight;
                total += weight;
            }
        }
    
        if (total > 0.0) {
            blurred /= total;
        }
    
        // Calculate the sharpened color using inputColor
        vec4 sharp = inputColor + clamp(inputColor - blurred, 0.0, 1.0) * amount;
        // Keep original alpha
        sharp.a = inputColor.a;
    
        // Ensure the sharp color does not go below 0 or above 1
        // This means: sharpening must happen AFTER tonemapping.
        sharp = clamp(sharp, 0.0, 1.0);
    
        outputColor = sharp;
    }
    
    `

    class _SharpeningEffect extends MODULES.POSTPROCESSING.MODULE!.Effect {
        constructor() {
            super("Sharpening", frag, {
                vertexShader: vert,
                blendFunction: MODULES.POSTPROCESSING.MODULE.BlendFunction.NORMAL,
                uniforms: new Map<string, Uniform<any>>([
                    ["amount", new Uniform(1)],
                    ["radius", new Uniform(1)],
                    // ["threshold", new Uniform(0)],
                ]),
                attributes: EffectAttribute.CONVOLUTION
            });
        }
    }
    return _SharpeningEffect;


}