import * as THREE from 'three'
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js'
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js'
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'
import * as TWEEN from 'three/examples/jsm/libs/tween.module.js' 

class Intro{

    #lettersZ=-1
    #onResizeCallbacks = []

    constructor(){
        this.canvas = document.querySelector('.intro3d')

        // Order : 
        // 1. Launch loading promises
        // 2. When they are all completed, initScene
        // 2.1 at the end of initScen, we add the class 'ready' to the canvas, which launches the CSS animation
        // 3. When that CS animation is finished, we can launch the 3D animation (rendering already started to have textures)
        this.canvas.addEventListener('animationend', this.launch3DAnim.bind(this))

        const loadHDR = () =>
            new Promise((resolve, reject) => {
                new RGBELoader()
                    .setPath('/app/assets/images/')
                    .load('kloofendal_48d_partly_cloudy_puresky_2k.hdr', resolve, null, reject)
            })

        const loadTexture1 = () => 
            new Promise((resolve, reject) => {
                new THREE.TextureLoader()
                    .setPath('/app/assets/images/')
                    .load('logop42-intro_three.png', resolve, undefined, reject)
            })
        
        const loadTexture2 = () => 
            new Promise((resolve, reject) => {
                new THREE.TextureLoader()
                    .setPath('/app/assets/images/')
                    .load('Logo-Emergence-bck.png', resolve, undefined, reject)
            })


        Promise.allSettled([loadHDR(), loadTexture1(), loadTexture2()]).then(this.initScene.bind(this))    
        this.Xcorrection = -0.02
    }

    initScene(LoadersResults){ 
        let hdrTexture
        const [hdrResult, texResultP42, texResultEE] = LoadersResults
        if((hdrResult.status === 'fulfilled') && (texResultP42.status === 'fulfilled') && (texResultEE.status === 'fulfilled')) {
            hdrTexture = hdrResult.value
            this.backgroundTextureP42 = texResultP42.value
            this.backgroundTextureEE = texResultEE.value

            hdrTexture.mapping = THREE.EquirectangularReflectionMapping
            this.backgroundTextureP42.colorSpace = THREE.SRGBColorSpace
            this.backgroundTextureEE.colorSpace = THREE.SRGBColorSpace

        } else {
            console.error('Loading error:', hdrResult, texResultP42, texResultEE)
            return
        }

        this.scene = new THREE.Scene()

        this.camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 100)
        this.camera.position.set(-2, -0.5, 4) //(0.1, 0.02, 4)
        this.camera.lookAt(0,0,0)

        this.renderer = new THREE.WebGLRenderer({
            antialias: true,
            alpha: true,
            canvas: this.canvas
        })

        hdrTexture.mapping = THREE.EquirectangularReflectionMapping
        this.scene.environment = hdrTexture

        this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
        this.renderer.setSize(window.innerWidth, window.innerHeight)
        this.renderer.setClearColor(0x000000, 0)
        this.renderer.outputColorSpace = THREE.SRGBColorSpace

        this.backgroundTextureP42.colorSpace = THREE.SRGBColorSpace
        this.backgroundTextureP42.wrapS = THREE.ClampToEdgeWrapping
        this.backgroundTextureP42.wrapT = THREE.ClampToEdgeWrapping
        this.backgroundTextureP42.generateMipmaps = false
        this.backgroundTextureP42.minFilter = THREE.LinearFilter
        this.backgroundTextureP42.magFilter = THREE.LinearFilter
        this.aspectRatioP42 = this.backgroundTextureP42.image.width / this.backgroundTextureP42.image.height

        this.backgroundTextureEE.colorSpace = THREE.SRGBColorSpace
        this.backgroundTextureEE.wrapS = THREE.ClampToEdgeWrapping
        this.backgroundTextureEE.wrapT = THREE.ClampToEdgeWrapping
        this.backgroundTextureEE.generateMipmaps = false
        this.backgroundTextureEE.minFilter = THREE.LinearFilter
        this.backgroundTextureEE.magFilter = THREE.LinearFilter
        this.aspectRatioEE = this.backgroundTextureEE.image.width / this.backgroundTextureEE.image.height
        
        this.makeOrResizePlane()
        this.#onResizeCallbacks.push(this.makeOrResizePlane.bind(this))

        // === Lights ===
        const l1 = new THREE.DirectionalLight(0xffbb33, 1)
        l1.position.set(-1, 1.5, 6)
        this.scene.add(l1)

        const l2 = new THREE.DirectionalLight(0xffbb33, 1)
        l2.position.set(2, -1.5, 6)
        this.scene.add(l2)
            
        this.lettersPosScale =[
            { position: { x: -0.98, y: 0.02, z: -1 }, scale:  { x: 1, y: 1.12, z: 1 } },
            { position: { x: -0.63, y: -0.04, z: -1.05 }, scale: { x: 1, y: 1.05, z: 1 } },
            { position: { x: -0.28, y: -0.05, z: -1.1 }, scale:  { x: 0.9, y: 1.15, z: 1, X: 0.8 } },
            { position: { x: -0.1, y: -0.04, z: -1.15 }, scale:  { x: 1, y: 1, z: 1 } },
            { position: { x: 0.12, y: -0.06, z: -1.2 }, scale: { x: 0.65, y: 1, z: 1 } },
            { position: { x: 0.355, y: -0.055, z: -1.25 }, scale: { x: 0.5, y: 0.9, z: 1, Y: 0.9 } },
            { position: { x: 0.53, y: -0.01, z: -1.3 }, scale: { x: 1, y: 0.94, z: 1 } },
            { position: { x: 0.84, y: -0.05, z: -1.35 }, scale: { x: 0.95, y: 1.04, z: 1 } },
            { position: { x: 1.17, y: -0.05, z: -1.4 }, scale:  { x: 0.7, y: 1.02, z: 1 } },
        ]      

        this.glassLetters = []
        const text = 'Project42'

        const fontLoader = new FontLoader()
        fontLoader.load('/app/thirdparty/Three/fonts/helvetiker_regular.typeface.json', font => {
            const mat = new THREE.MeshPhysicalMaterial({
                metalness: 0.05,
                roughness: 0.02,
                transmission: 1,
                thickness: 0.8,
                ior: 1.45,
                envMapIntensity: 1.5,
                attenuationColor: new THREE.Color(0x88ffcc),
                attenuationDistance: 2,   
                color: 0x88ffcc, 
                depthWrite: true,
                depthTest: true,
                transparent: true,
            })

            for (let i = 0; i < text.length; i++) {
                const char = text[i]
                const geo = new TextGeometry(char, {
                    font,
                    size: .5,
                    height: 0.15,
                    curveSegments: 32,
                    bevelEnabled: true,
                    bevelThickness: 0.03,
                    bevelSize: 0.03,
                    bevelSegments: 32,
                    renderOrder: 10,
                })
                
                geo.center() 
                const mesh = new THREE.Mesh(geo, mat)
                mesh.position.set(this.lettersPosScale[i].position.x + this.Xcorrection, this.lettersPosScale[i].position.y, this.lettersPosScale[i].position.z)
                mesh.scale.set(this.lettersPosScale[i].scale.x, this.lettersPosScale[i].scale.y, this.lettersPosScale[i].scale.z)
                this.scene.add(mesh)
                this.glassLetters.push(mesh)
            }

        })

        
        // === Resize handling ===
        window.addEventListener('resize', () => {
            this.camera.aspect = window.innerWidth / window.innerHeight
            this.camera.updateProjectionMatrix()
            this.renderer.setSize(window.innerWidth, window.innerHeight)
            this.#onResizeCallbacks.forEach(fn => fn())
        }) 

        this.renderer.render(this.scene, this.camera)
        document.querySelector('div.loading').style.display = 'none'
        this.canvas.classList.add('ready')

    }

    launch3DAnim(){ console.log('CSs finished, launching 3D anim')
        document.getElementById('startbtn').style.display = 'block'
        this.nextAnim = Date.now()
        this.animate()
    }

    makeOrResizePlane() {
        const planeDistance = 4.4
        const fovRad = THREE.MathUtils.degToRad(this.camera.fov)
        const viewHeight = 2 * Math.tan(fovRad / 2) * planeDistance
        const viewWidth = viewHeight * this.camera.aspect
        
        let planeWidth, planeHeight
        if (viewWidth / viewHeight > this.aspectRatioP42) {
            planeHeight = viewHeight
            planeWidth = planeHeight * this.aspectRatioP42
        } else {
            planeWidth = viewWidth
            planeHeight = planeWidth / this.aspectRatioP42
        }        
        if (!this.planeP42) {
            const geo = new THREE.PlaneGeometry(planeWidth, planeHeight)
            const mat = new THREE.MeshBasicMaterial({ map: this.backgroundTextureP42, transparent: false, depthWrite:true, depthTest:true })
            this.planeP42 = new THREE.Mesh(geo, mat)
            this.planeP42.scale.set(1.44, 1.5, 1)
            this.planeP42.position.x = this.Xcorrection
            this.planeP42.position.z = 0
            this.planeP42.renderOrder = 0
            this.scene.add(this.planeP42)
        } else {
            this.planeP42.geometry.dispose()
            this.planeP42.geometry = new THREE.PlaneGeometry(planeWidth, planeHeight)
        }

        if (viewWidth / viewHeight > this.aspectRatioEE) {
            planeHeight = viewHeight
            planeWidth = planeHeight * this.aspectRatioEE
        } else {
            planeWidth = viewWidth
            planeHeight = planeWidth / this.aspectRatioEE
        }    
        if (!this.planeEE) {
            const geo = new THREE.PlaneGeometry(planeWidth, planeHeight)
            this.matEE = new THREE.MeshBasicMaterial({ map: this.backgroundTextureEE, transparent: false, depthWrite:true, depthTest:true })
            this.matEE.defines = this.matEE.defines || {}
            this.matEE.defines.USE_UV = ""
            this.matEE.onBeforeCompile = shader => {
                shader.uniforms.uReveal = { value: -0.3 }  
                shader.uniforms.uTime = { value: 0 };
                shader.fragmentShader = shader.fragmentShader
                 .replace(
                    `#include <uv_pars_fragment>`,
                    `#include <uv_pars_fragment>
                     uniform float uReveal;
                     uniform float uTime;
                    `
                )                
                .replace(
                    `#include <envmap_fragment>`,
                    `#include <envmap_fragment>

                    float bandWidth = 0.20;

                    if (vUv.x > uReveal) {
                        float d = (vUv.x - uReveal) / bandWidth; // 0 at front, grows to the right
                        if (d < 1.0) { // inside noisy band                        
                            float density = smoothstep(1.0, 0.0, d);
                            float random = fract( sin(dot(gl_FragCoord.xy + vec2(uTime), vec2(12.9898,78.233))) * 43758.5453 );
                            if (random > density) discard;
                        } else {
                            // fully hidden
                            discard;
                        }

                    }                 
                    `
                )
                this.matEE.userData.shader = shader
            }
           
            this.planeEE = new THREE.Mesh(geo, this.matEE)
            this.planeEE.scale.set(.2, .2, 1)
            this.planeEE.position.set(0,-0.8,.1)
            this.planeEE.renderOrder = 1
            this.scene.add(this.planeEE)
        } else {
            this.planeEE.geometry.dispose()
            this.planeEE.geometry = new THREE.PlaneGeometry(planeWidth, planeHeight)
        }


    }

    animate() {
        TWEEN.update()
        this.camera.lookAt(0,0,0)
        requestAnimationFrame(this.animate.bind(this))
        if (this.matEE.userData.shader) {
            if(this.matEE.userData.shader.uniforms.uReveal.value<1){
                this.matEE.userData.shader.uniforms.uReveal.value += 0.005
                this.matEE.userData.shader.uniforms.uTime.value = Math.floor(performance.now() / 200)
            }
        }
        if(Date.now()>this.nextAnim){


            this.#lettersZ += .005
            for(const letter of this.glassLetters){
                letter.position.z += 0.005 
            }

            if(this.#lettersZ>0.5){
                 if(!this.camTween){
                    this.camTween = new TWEEN.Tween(this.camera.position)
                        .to({   x: 0.1,
                                y: 0.02,
                                z: 4, 
                            }, 7000)
                        .easing(TWEEN.Easing.Sinusoidal.InOut)
                        .start()
                }    
            }

            if(this.#lettersZ>1.5) {            
                for(const [i, letter] of this.glassLetters.entries()){
                    letter.rotation.x += Math.abs(i-4)/-1000
                    letter.rotation.y += (i-4)/2000
                    letter.position.x += (i-4)/4000
                }
            }
    
            if(this.#lettersZ>4.5) { 
                this.#lettersZ = -1
                for(const [i, letter] of this.glassLetters.entries()){
                    letter.rotation.y = 0
                    letter.rotation.x = 0
                    letter.position.x = this.lettersPosScale[i].position.x + this.Xcorrection
                    letter.position.z = this.lettersPosScale[i].position.z
                }
                this.nextAnim = Date.now()+10000
            }
        }

        this.renderer.render(this.scene, this.camera)
    }

}

new Intro()


