import * as THREE from 'three'
// import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.module.js'
// import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.module.js'
// import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.module.js'
// import { ShaderPass } from 'three/examples/jsm/shaders/GammaCorrectionShader.module.js'
// import { GammaCorrectionShader } from 'three/examples/jsm/shaders/GammaCorrectionShader.module.js'


if(!app.helpers) app.helpers = {}
/**
* Mixing add-in methods to your view instance.
* All of this should not be a helper, but inherited this from WindozDomContent, but not my framework anymore.
*/
app.helpers.helpers3D = {
    
    agentFromJSON(id, desc){ 
        let obj, wrapper
        if(desc.type === 'Mesh') {
            const geom = new THREE[desc.geometry.type](...(desc.geometry.args || []))
            const matType = desc.material.type
            const matProps = { ...desc.material }
            for (const key in matProps) {
                if (key === 'type') continue
                if (typeof matProps[key] === 'string' && THREE[matProps[key]] !== undefined) {
                    matProps[key] = THREE[matProps[key]]
                }
            }
            // convert color strings like "0xffffaa" to numbers
            if (typeof matProps.color === 'string' && matProps.color.startsWith('0x')) {
                matProps.color = parseInt(matProps.color)
            }
            const mat = new THREE[matType](matProps)
            
            
            obj = new THREE.Mesh(geom, mat)
            if(desc.translate){
                wrapper = new THREE.Object3D()
                wrapper.add(obj)
                obj.position.x = desc.translate[0]
                obj.position.y = desc.translate[1]
                obj.position.z = desc.translate[2]
            }
            
        } else if(desc.type === 'Group') {
            obj = new THREE.Group()
        } else {
            throw new Error("Unknown type: " + desc.type)
        }
        
        // Apply transforms
        if(desc.position) obj.position.set(...desc.position)
            if(desc.rotation) obj.rotation.set(...desc.rotation)
                if(desc.scale) obj.scale.set(...desc.scale)
                    
        // Recursively add children
        if(desc.children) {
            desc.children.forEach(childDesc => {
                const childId = (childDesc.childSuffix) ? `${id}_${childDesc.childSuffix}` : ''
                obj.add(this.agentFromJSON(childId, childDesc))
            })
        }
        if(wrapper) obj=wrapper
        obj.name = id
        return obj
    },
    
    resizeRendererToDisplaySize() {
        const width  = this.canvasEl.clientWidth
        const height = this.canvasEl.clientHeight        
        // Check if renderer size differs from displayed size
        const needResize = this.canvasEl.width !== width || this.canvasEl.height !== height        
        if (needResize) {
            // 1. Update renderer (base framebuffer)
            this.renderer.setSize(width, height, false)

            // 2️. Update camera aspect ratio
            this.camera.aspect = width / height
            this.camera.updateProjectionMatrix()
        }      
        return needResize
    },
    
    cameraAutoFrame(object, camera, offset = 1.5, controls) {
        const box = new THREE.Box3().setFromObject(object)
        const size = new THREE.Vector3()
        const center = new THREE.Vector3()
        box.getSize(size)
        box.getCenter(center)
        
        const maxDim = Math.max(size.x, size.y, size.z)
        const fov = camera.fov * Math.PI / 180
        let cameraZ = maxDim / (2 * Math.tan(fov / 2)) * offset
        
        camera.position.copy(center)
        camera.position.z += cameraZ
        camera.lookAt(center)
        
        if (controls) {
            controls.target.copy(center)
            controls.update()
        }
    },
    
    getObjectCenter(object) {
        object.updateWorldMatrix(true, true)
        const box = new THREE.Box3().setFromObject(object)
        const center = new THREE.Vector3()
        box.getCenter(center) // world coords
        return center
    },

    getObjectTopCenter(object, offset = 0.1) {
        object.updateWorldMatrix(true, true)
        const box = new THREE.Box3().setFromObject(object)
        const center = new THREE.Vector3()
        box.getCenter(center)
        const up = new THREE.Vector3(0, 1, 0).applyQuaternion(object.quaternion).normalize()
        const height = box.max.y - box.min.y
        const top = center.clone().addScaledVector(up, height / 2 + offset)
        return top
    },    

    
    outlineMaterial: new THREE.MeshBasicMaterial({
        color: 0xFFFFFF,
        side: THREE.BackSide,
        transparent: true,   
        opacity: 0.8, 
        depthTest: true,
        depthWrite: false,
        stencilWrite: true,
        stencilFunc: THREE.AlwaysStencilFunc,
        stencilRef: 1,
        stencilMask: 0xff,
        stencilFail: THREE.KeepStencilOp,
        stencilZFail: THREE.KeepStencilOp,
        stencilZPass: THREE.ReplaceStencilOp
    }),

    normalStencilMat: new THREE.MeshBasicMaterial({
        color: 0xffffff,
        stencilWrite: true,
        stencilRef: 0,
        stencilFunc: THREE.NotEqualStencilFunc,
        stencilFail: THREE.KeepStencilOp,
        stencilZFail: THREE.KeepStencilOp,
        stencilZPass: THREE.KeepStencilOp
    }),

    highlight3DObj(obj, scene) {
        this.highlightedObj = obj
        // Scaled up Mesh to stencil
        obj.traverse(child => {
            if (child.isMesh) {
                const highlightInflatedMesh = new THREE.Mesh(child.geometry, this.outlineMaterial)
                highlightInflatedMesh.position.copy(child.getWorldPosition(new THREE.Vector3()))
                highlightInflatedMesh.quaternion.copy(child.getWorldQuaternion(new THREE.Quaternion()))
                highlightInflatedMesh.scale.copy(child.getWorldScale(new THREE.Vector3())).multiplyScalar(1.05)

                scene.add(highlightInflatedMesh)
                child._outlineMesh = highlightInflatedMesh
            }
        })

        // Normal Mesh render only if stencil != 1
        obj.traverse(child => {
            if (child.isMesh) {
                child._savedMaterial = child.material
                child.material = this.normalStencilMat
            }
        })
    },

    clearHighlight3DObj(obj,scene) {
        this.highlightedObj = null
        obj.traverse(child => {
            if (child._outlineMesh) {
                scene.remove(child._outlineMesh)
                child._outlineMesh.geometry.dispose()
                child._outlineMesh.material.dispose()
                delete child._outlineMesh
            }

            if (child._savedMaterial) {
                child.material = child._savedMaterial
                delete child._savedMaterial
            }
        })
    },

    animateHighlight3DObj() {
        if(!this.highlightedObj) return
        const t = performance.now() * 0.002
        const scale = 1.05 + Math.sin(t * 2) * 0.03
        this.highlightedObj.traverse(child => {
            if (child._outlineMesh) {
                child._outlineMesh.scale.set(scale, scale, scale)
            }
        })
    },

    makePivotAtGeomCenter(object, scene) {
        object.updateWorldMatrix(true, true)
        const box = new THREE.Box3().setFromObject(object)
        const centerW = box.getCenter(new THREE.Vector3())
        
        // Create pivot at world center of the object
        const pivot = new THREE.Object3D()
        pivot.position.copy(centerW)
        pivot.matrixAutoUpdate = true
        scene.add(pivot)
        
        // Compute the center in the object's local space
        const centerLocal = object.worldToLocal(centerW.clone())
        
        // Shift the object so its center sits at its own origin
        object.position.sub(centerLocal)
        
        // Reparent under the pivot (world pose preserved)
        pivot.add(object)
        
        return pivot
    },
    
    getNamedParent(obj) {
        while (obj && ((!obj.name) || (obj.name.includes('_'))) ) {
            obj = obj.parent
        }
        return obj
    },

    createArrow(origin, vector, options){
        options.name = options.name || ''
        options.color = options.color || 0xffaa00
        options.headLength = options.headLength || 0.25
        options.headWidth = options.headWidth || 0.1
        options.scale = options.scale || 1
        //const from = new THREE.Vector3(origin.x, origin.y, origin.z)
        const dir = new THREE.Vector3(vector.x, vector.y, vector.z).normalize()
        let length = Math.sqrt(vector.x**2 + vector.y**2 + vector.z**2)
        if(length==0) return(null)
        length *= options.scale
        const arrow = new THREE.ArrowHelper(dir, origin, length, options.color, options.headLength, options.headWidth)
        
        // Optional: add a subtle tube body for style
        const shaftGeo = new THREE.CylinderGeometry(0.02, 0.02, length - options.headLength, 16)
        const shaftMat = new THREE.MeshStandardMaterial({ color: options.color, metalness: 0.3, roughness: 0.2 })
        const shaft = new THREE.Mesh(shaftGeo, shaftMat)
        shaft.position.copy(origin.clone().add(dir.clone().multiplyScalar((length - options.headLength) / 2)))
        shaft.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir)
        
        const group = new THREE.Group()
        group.add(arrow)
        group.add(shaft)
        if(options.name) group.name = options.name
        this.scene.add(group)
        return group
    },
}
