import { ShaderMaterial, Texture, Vector2, Vector3 } from "three";
import { useEffect, useMemo } from "react";
import { useThree } from "@react-three/fiber";
import { DEFAULT_CONFIG, REFRESH_RATE } from "../constant";

import baseVertex from "../glsl/base.vert?raw";
import clearFrag from "../glsl/clear.frag?raw";
import curlFrag from "../glsl/curl.frag?raw";
import divergenceFrag from "../glsl/divergence.frag?raw";
import gradientSubstractFrag from "../glsl/gradientSubstract.frag?raw";
import pressureFrag from "../glsl/pressure.frag?raw";
import splatFrag from "../glsl/splat.frag?raw";
import advectionFrag from "../glsl/advection.frag?raw";
import vorticityFrag from "../glsl/vorticity.frag?raw";

export const useMaterials = (): { [key: string]: ShaderMaterial } => {
  const size = useThree((s) => s.size);

  const shaderMaterials = useMemo(() => {
    const advection = new ShaderMaterial({
      name: "Fluid/Advection",
      uniforms: {
        uVelocity: {
          value: new Texture(),
        },
        uSource: {
          value: new Texture(),
        },
        dt: {
          value: 1 / REFRESH_RATE,
        },
        uDissipation: {
          value: 1.0,
        },
        texelSize: { value: new Vector2() },
      },
      fragmentShader: advectionFrag,
      vertexShader: baseVertex,
      defines: {
        USE_V_UV: "",
      },
      depthTest: false,
      depthWrite: false,
    });

    const clear = new ShaderMaterial({
      name: "Fluid/Clear",
      uniforms: {
        uTexture: {
          value: new Texture(),
        },
        uClearValue: {
          value: DEFAULT_CONFIG.pressure,
        },
        texelSize: {
          value: new Vector2(),
        },
      },
      fragmentShader: clearFrag,
      vertexShader: baseVertex,
      defines: {
        USE_V_UV: "",
      },
      depthTest: false,
      depthWrite: false,
    });

    const curl = new ShaderMaterial({
      name: "Fluid/Curl",
      uniforms: {
        uVelocity: {
          value: new Texture(),
        },
        texelSize: {
          value: new Vector2(),
        },
      },
      fragmentShader: curlFrag,
      vertexShader: baseVertex,
      defines: {
        USE_OFFSETS: "",
      },
      depthTest: false,
      depthWrite: false,
    });

    const divergence = new ShaderMaterial({
      name: "Fluid/Divergence",
      uniforms: {
        uVelocity: {
          value: new Texture(),
        },
        texelSize: {
          value: new Vector2(),
        },
      },
      fragmentShader: divergenceFrag,
      vertexShader: baseVertex,
      defines: {
        USE_V_UV: "",
        USE_OFFSETS: "",
      },
      depthTest: false,
      depthWrite: false,
    });

    const gradientSubstract = new ShaderMaterial({
      name: "Fluid/GradientSubtract",
      uniforms: {
        uPressure: {
          value: new Texture(),
        },
        uVelocity: {
          value: new Texture(),
        },
        texelSize: {
          value: new Vector2(),
        },
      },
      fragmentShader: gradientSubstractFrag,
      vertexShader: baseVertex,
      defines: {
        USE_V_UV: "",
        USE_OFFSETS: "",
      },
      depthTest: false,
      depthWrite: false,
    });

    const pressure = new ShaderMaterial({
      name: "Fluid/Pressure",
      uniforms: {
        uPressure: {
          value: new Texture(),
        },
        uDivergence: {
          value: new Texture(),
        },
        texelSize: {
          value: new Vector2(),
        },
      },
      fragmentShader: pressureFrag,
      vertexShader: baseVertex,
      defines: {
        USE_V_UV: "",
        USE_OFFSETS: "",
      },
      depthTest: false,
      depthWrite: false,
    });

    const splat = new ShaderMaterial({
      name: "Fluid/Splat",
      uniforms: {
        uTarget: {
          value: new Texture(),
        },
        aspectRatio: {
          value: size.width / size.height,
        },
        uColor: {
          value: new Vector3(),
        },
        uPointer: {
          value: new Vector2(),
        },
        uRadius: {
          value: DEFAULT_CONFIG.radius / 100.0,
        },
        texelSize: {
          value: new Vector2(),
        },
      },
      fragmentShader: splatFrag,
      vertexShader: baseVertex,
      defines: {
        USE_V_UV: "",
      },
      depthTest: false,
      depthWrite: false,
    });

    const vorticity = new ShaderMaterial({
      name: "Fluid/Vorticity",
      uniforms: {
        uVelocity: {
          value: new Texture(),
        },
        uCurl: {
          value: new Texture(),
        },
        uCurlValue: {
          value: DEFAULT_CONFIG.curl,
        },
        dt: {
          value: 1 / REFRESH_RATE,
        },
        texelSize: {
          value: new Vector2(),
        },
      },
      fragmentShader: vorticityFrag,
      vertexShader: baseVertex,
      defines: {
        USE_V_UV: "",
        USE_OFFSETS: "",
      },
      depthTest: false,
      depthWrite: false,
    });

    return {
      splat,
      curl,
      clear,
      divergence,
      pressure,
      gradientSubstract,
      advection,
      vorticity,
    };
  }, [size]);

  useEffect(() => {
    for (const material of Object.values(shaderMaterials)) {
      const aspectRatio = size.width / (size.height + 400);
      material.uniforms.texelSize.value.set(
        1 / (DEFAULT_CONFIG.simRes * aspectRatio),
        1 / DEFAULT_CONFIG.simRes,
      );
    }

    return () => {
      for (const material of Object.values(shaderMaterials)) {
        material.dispose();
      }
    };
  }, [shaderMaterials, size]);

  return shaderMaterials;
};
