import * as React from "react";
import * as THREE from "three";
import { easing } from "maath";
import { useFrame } from "@react-three/fiber";
import CSM from "three-custom-shader-material";

const vertexShader = `
    varying vec2 vUv;
    varying vec3 vPosition;
    void main() {
        vUv = uv;
        vPosition = position;
    }
`;

const fragmentShader = `
    varying vec2 vUv;
    varying vec3 vPosition;
    uniform float uThickness;
    uniform vec3 uColor;
    uniform float uProgress;
    uniform vec3 uInverseScale;

    float hash(vec3 p) {
        p = fract(p * 0.3183099 + 0.1);
        p *= 17.0;
        return fract(p.x * p.y * p.z * (p.x + p.y + p.z));
    }

    float noise(vec3 x) {
        vec3 i = floor(x);
        vec3 f = fract(x);
        f = f * f * (3.0 - 2.0 * f);
    
        return mix(
            mix(mix(hash(i + vec3(0,0,0)), hash(i + vec3(1,0,0)), f.x),
                mix(hash(i + vec3(0,1,0)), hash(i + vec3(1,1,0)), f.x), f.y),
            mix(mix(hash(i + vec3(0,0,1)), hash(i + vec3(1,0,1)), f.x),
                mix(hash(i + vec3(0,1,1)), hash(i + vec3(1,1,1)), f.x), f.y), f.z);
    }

    void main() {
        float noiseValue = noise(vPosition * uInverseScale * 10.0);
        float progress = uProgress;

        float visualFade = 1.0 - smoothstep(0.9, 1.0, progress);

        float dissolveProgress = 1.0 - progress;

        float alpha = step(noiseValue, dissolveProgress);

        float border = step(noiseValue, dissolveProgress + uThickness) - alpha;
        border *= step(0.0, dissolveProgress);

        alpha *= visualFade;
        border *= visualFade;

        csm_DiffuseColor.rgb = border > 0.0 ? uColor : csm_DiffuseColor.rgb;
        csm_DiffuseColor.a = alpha + border;

        if (progress >= 1.0) {
            csm_DiffuseColor.a = 0.0;
        }
    }
`;

interface DissolveMaterialProps {
    baseMaterial: THREE.ShaderMaterial | THREE.MeshStandardMaterial | THREE.MeshPhysicalMaterial;
    scale?: [number, number, number];
    thickness?: number;
    color?: string;
    intensity?: number;
    duration?: number;
    fade?: boolean;
    fadedIn?: () => void;
    fadedOut?: () => void;
}

export function DissolveMaterial({ baseMaterial, scale = [1, 1, 1], thickness = 0.1, color = "#0082b2", intensity = 50, duration = 1.2, fade = true, fadedIn, fadedOut }: DissolveMaterialProps) {
    const uniforms = React.useRef({
        uThickness: { value: thickness },
        uColor: { value: new THREE.Color(color).multiplyScalar(intensity) },
        uProgress: { value: fade ? 1 : 0 },
        uInverseScale: { value: new THREE.Vector3(...scale) },
    });

    const hasFadedIn = React.useRef(false);
    const hasFadedOut = React.useRef(false);

    useFrame((_state, delta) => {
        easing.damp(uniforms.current.uProgress, "value", fade ? 0 : 1, duration, delta);

        const p = uniforms.current.uProgress.value;

        if (p <= 0.01) {
            if (!hasFadedIn.current) {
                fadedIn?.();
                hasFadedIn.current = true;
                hasFadedOut.current = false;
            }
        } else if (p >= 0.99) {
            if (!hasFadedOut.current) {
                fadedOut?.();
                hasFadedOut.current = true;
                hasFadedIn.current = false;
            }
        }
    });

    return <CSM baseMaterial={baseMaterial} vertexShader={vertexShader} fragmentShader={fragmentShader} uniforms={uniforms.current} toneMapped={false} transparent />;
}
