<script setup lang="ts">
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
import { Box3, Color, DoubleSide, Group, Mesh, MeshBasicMaterial, PlaneGeometry, SpriteMaterial, TextureLoader, Vector2, Vector3 } from 'three';
import { useTresContext, useSeek, useRenderLoop } from '@tresjs/core';
import { useGLTF } from '@tresjs/cientos'

import Env from './env.vue'

import { IMAGE_URL, SERVER_URL, } from '../../constants'
import { usePromoSidebar } from '../../stores/promo_sidebar';
import { usePromoScene } from '../../stores/promo_scene';

const props = defineProps(['source', 'loaded', 'loaded_pan'])

function shadows_and_pos(scene: any) {
    scene.children.forEach((el: any) => {
        el.receiveShadow = true
        el.castShadow = true
        if(el.isPointLight) {
            el.shadow.bias = -0.01
        }
        shadows_and_pos(el)
    })
}

const models = ref<model3DType[]>([])
const clickable = ref<clickableAreaType[]>([])
const clickable_items = ref<any[]>([])
const clickable_refs = ref<any[]>([])
const sidebar = usePromoSidebar();
const sidebar_scene = usePromoScene()
const { renderer, controls, camera, scene, raycaster } = useTresContext()
const { seekByName, seekAllByName } = useSeek()
const envVars = reactive({}) as { hdr_gainmap?: string, hdr_json?: string, hdr_webp?: string }

// renderer.value.capabilities.maxTextures = 4
// renderer.value.capabilities.maxTextureSize = 512
// renderer.value.capabilities.precision = 'lowp'

const loadModels = async () => {
    const res = await fetch(`${SERVER_URL}/api/obj/scene/${props.source}`)
    const raw_data = await res.json() as scene3D

    envVars.hdr_gainmap = raw_data.hdr_gainmap ? `${IMAGE_URL}/${raw_data.hdr_gainmap}` : undefined
    envVars.hdr_json = raw_data.hdr_json ? `${IMAGE_URL}/${raw_data.hdr_json}` : undefined
    envVars.hdr_webp = raw_data.hdr_webp ? `${IMAGE_URL}/${raw_data.hdr_webp}` : undefined

    const data = raw_data.elements
    if (!controls.value) return;

    camera.value?.position.set(1, 1, 1);
    controls.value.enabled = false;
    (controls.value as any).minDistance = raw_data.min_distance;
    (controls.value as any).maxDistance = raw_data.max_distance;
    (controls.value as any)._needsUpdate = true;
    (controls.value as any).update()

    const sidebar_items = []
    clickable_items.value = []
    for (let index = 0; index < data.length; index++) {
        const element = data[index];
        sidebar_items.push({ ...element, is_enabled: true })
        const item = {} as model3DType

        item.modelUrl = `${IMAGE_URL}/${element.model_file}`
        let { scene: loaded_scene } = await useGLTF(item.modelUrl)
        shadows_and_pos(loaded_scene)
        item.modelFile = loaded_scene
        item.name = element.name

        models.value.push(item)

        const res = await fetch(`${SERVER_URL}/api/obj/clickable/?source=${element.id}`)
        const clickable_areas = await res.json()
        clickable.value.push(...clickable_areas)
    }
    sidebar_scene.setData(sidebar_items)
    // sidebar.open()

    for (let index = 0; index < clickable.value.length; index++) {
        const element = clickable.value[index];
        const find_element = seekByName(scene.value, element.object_name)
        if (!find_element) continue
        // const res_array = (find_element as Group).isGroup ? find_element?.children : [find_element]
        if (find_element && !(find_element as Group).isGroup) {
            const world_position = new Vector3();
            ((find_element as Mesh).geometry.boundingBox as any).getCenter(world_position);
            (find_element as Mesh).localToWorld(world_position);

            const p = raw_data.min_distance * 0.05
            const plane = new PlaneGeometry(p, p, 32)

            const mesh_material = new MeshBasicMaterial({ side: DoubleSide })
            const sprite_material = new SpriteMaterial()
            if (element.image) {
                const map = new TextureLoader().load(`${IMAGE_URL}/${element.image}`);
                mesh_material.map = map
                sprite_material.map = map
            } else {
                mesh_material.color = new Color('red')
                sprite_material.color = new Color('red')
            }

            const point = new Mesh(plane, mesh_material);
            // const point = new Sprite(sprite_material)
            point.position.set(world_position.x, p * 3, world_position.z)
            // point.scale.set(p, p, 1)
            point.name = `${element.id}_clickable`
            point.renderOrder = 10

            if (clickable_items.value.find(el => el.name == point.name)) continue
            clickable_items.value.push(point)
            clickable_refs.value.push(ref(`${element.id}_clickable`))
        }
    }

    const loaded = seekByName(scene.value, 'loaded')
    if (loaded) {
        const box = new Box3();
        box.expandByObject(loaded);
        const box_size = new Vector3();
        box.getSize(box_size)
        props.loaded_pan(
            new Vector3(box_size.x * 0.5, box_size.y * 0.5, box_size.z * 0.5),
            new Vector3(box_size.x * -0.5, box_size.y * -0.5, box_size.z * -0.5),
        )
    }

    controls.value.enabled = true;
    props.loaded()
    console.log("Number of Triangles :", renderer.value.info.render.triangles);
    console.log("render.calls ", renderer.value.info.render.calls);

}

const { onLoop } = useRenderLoop()
onLoop(() => {
    clickable_refs.value.map(el => {
        // el.quaternion.copy(camera.value?.quaternion);
        if (el.value[0] && typeof el.value[0].lookAt == 'function') {
            el.value[0].lookAt(camera.value?.position)
        }
    })
})

const openSidebar = (id: number) => {
    const target = clickable.value.find(el => el.id == id)
    if (!target) return
    const sidebar_data = {
        title: target.name,
        description: target.description
    } as PromoSidebarData
    if (target?.target) {
        sidebar_data.target = target.target.toString()
        sidebar_data.target_name = target.target_name
    }
    sidebar.setData(sidebar_data)
    sidebar.open()
}

loadModels()
watch(() => props.source, () => {
    const loaded = seekByName(scene.value, 'loaded')
    if (loaded) {
        loaded.children = []
    }
    sidebar.close()
    loadModels()
})

onMounted(() => {
    document.addEventListener('click', clickEvent)
    console.log(sidebar.is_open)
    if (sidebar.is_open) {
        sidebar.close()
    }
})
onUnmounted(() => { document.removeEventListener('click', clickEvent) })
const pointer = reactive({ x: 0, y: 0 })
const clickEvent = (event: MouseEvent) => {
    const x = (event.clientX / window.innerWidth) * 2 - 1
    const y = - (event.clientY / window.innerHeight) * 2 + 1
    if (x == pointer.x && y == pointer.y) return
    if (!camera.value) return

    pointer.x = x
    pointer.y = y
    raycaster.value.setFromCamera(new Vector2(pointer.x, pointer.y), camera.value);

    const intersects = raycaster.value.intersectObjects(seekAllByName(scene.value, '_clickable'));
    const names = intersects.map(el => el.object.name ?? false).filter(Boolean)
    if (names.length) {
        openSidebar(parseInt(names[0].replace('_clickable', '')))
    }
}

watch(() => sidebar_scene.list, () => {
    sidebar_scene.list.forEach(element => {
        const el = seekByName(scene.value, element.name)
        if (!el) return
        if (el.visible !== element.is_enabled) {
            el.visible = element.is_enabled
        }
    });
}, { deep: true })
</script>
<template>
    <TresGroup name="loaded">
        <Env v-bind="envVars" />
        <template v-for="item in models">
            <TresGroup :name="item.name">
                <TresObject3D v-bind="item.modelFile.clone()"/>
            </TresGroup>
        </template>
        <template v-for="(item, i) in clickable_items">
            <TresMesh v-bind="item" :ref="clickable_refs[i]" />
        </template>
    </TresGroup>
</template>