<script setup lang="ts">
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
import {
    Box3, Color, DoubleSide, Group, Mesh, PlaneGeometry,
    MeshStandardMaterial, MeshStandardMaterialParameters,
    Vector2, Vector3,
} from 'three';

import { useTresContext, useSeek, useTexture, useLoop } from '@tresjs/core';
import { useGLTF } from '@tresjs/cientos'

import Env from './env.vue'

import { IMAGE_URL, PROMOBG, SERVER_URL, } from '../../constants'
import { usePromoSidebar } from '../../stores/promo_sidebar';
import { usePromoScene } from '../../stores/promo_scene';
import { useClickable } from '../../stores/clickable';
import { useLoading } from '../../stores/loading';
import { mobileAndTabletCheck } from '../../helpers';

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

const models = ref<model3DType[]>([])
const clickable_items = ref<any[]>([])
const clickable_refs = ref<any[]>([])
const envVars = reactive({}) as EnvVars

const sidebar = usePromoSidebar();
const sidebar_scene = usePromoScene();
const clickable = useClickable()
const loading_store = useLoading()

const { controls, camera, scene, raycaster, renderer } = useTresContext()
const { pause, resume } = useLoop()
const { seekByName, seekAllByName } = useSeek()

const groundTexture = await useTexture({
    displacementMap: '/ground_displacement.jpg',
})

const { scene: pointer_pin } = await useGLTF('/pointer.glb')

const timer = ref(10)
setInterval(() => {
    if (timer.value > 0) {
        timer.value -= 1
    } else if (timer.value == 0 && !(controls.value as any).autoRotate && (controls.value as any).enabled) {
        pause()
        if (controls.value && camera.value) {
            camera.value?.position.set(
                (controls.value as any).maxDistance * 0.5,
                (controls.value as any).maxDistance * 0.5,
                (controls.value as any).maxDistance * 0.5
            );
            (controls.value as any).target = new Vector3(0, 0, 0);
            (controls.value as any).autoRotate = true;
            (controls.value as any).autoRotateSpeed = 0.5;
        }
        resume()
    }
}, 1000);

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

    envVars.focus = raw_data.max_distance * 0.5
    if (raw_data.env) {
        Object.assign(envVars, raw_data.env)
    } else {
        delete envVars.env_displacementmap
        delete envVars.env_normalmap
        delete envVars.hdr_gainmap
        delete envVars.hdr_json
        delete envVars.hdr_webp
        envVars.clear_color = PROMOBG
    }

    loading_store.status = 'env'
    const data = raw_data.elements
    if (!controls.value) return;

    controls.value.enabled = false;

    (controls.value as any).minDistance = mobileAndTabletCheck() ? raw_data.min_distance * 0.5 : raw_data.min_distance;
    (controls.value as any).maxDistance = raw_data.max_distance;

    camera.value?.position.set(
        (controls.value as any).maxDistance * 0.5,
        (controls.value as any).maxDistance * 0.5,
        (controls.value as any).maxDistance * 0.5
    );
    (controls.value as any).target = new Vector3(0, 0, 0);
    (controls.value as any).autoRotate = false;

    (controls.value as any)._needsUpdate = true;
    (controls.value as any).update()

    const sidebar_items = [] as PromoScene[]
    clickable_items.value = []

    loading_store.status = 'model'
    for (let index = 0; index < data.length; index++) {
        loading_store.count = index
        const element = data[index];
        const item = {} as model3DType

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

        if (!element.is_enabled) {
            item.modelFile.visible = false
        }

        if (item.modelFile.children[0]) {
            item.modelFile.children[0].position.set(
                item.modelFile.children[0].position.x + element.x_pos,
                item.modelFile.children[0].position.y + element.y_pos,
                item.modelFile.children[0].position.z + element.z_pos
            )
        }

        models.value.push(item)

        const res = await fetch(`${SERVER_URL}/api/obj/clickable/?source=${element.id}`)
        const clickable_areas = await res.json()
        clickable.list.push(...clickable_areas)
    }

    let c = new Color()
    if (envVars.clear_color) {
        c.set(envVars.clear_color)
    } else {
        renderer.value.getClearColor(c)
    }
    const tex = {} as any
    if (envVars.env_displacementmap) { tex.displacementMap = `${IMAGE_URL}/${envVars.env_displacementmap}` }
    if (envVars.env_normalmap) { tex.normalMap = `${IMAGE_URL}/${envVars.env_normalmap}` }
    let addTexture: any
    if (Object.keys(tex).length > 0) {
        addTexture = await useTexture(tex)
    }

    if (!models.value.find(el => el.name == 'ground')) {
        loading_store.status = 'ground'
        const mesh = {
            color: c.offsetHSL(0, 0.5, -0.33),
            displacementScale: envVars.focus * 0.33,
            roughness: 100,
            side: DoubleSide
        } as MeshStandardMaterialParameters
        if (envVars.env_displacementmap) {
            mesh.displacementMap = addTexture.displacementMap
        } else {
            mesh.displacementMap = groundTexture.displacementMap
        }
        if (envVars.env_normalmap) {
            mesh.normalMap = addTexture.normalMap
        }
        const ground = new Mesh(
            new PlaneGeometry(envVars.focus * 7, envVars.focus * 7, 1024, 1024),
            new MeshStandardMaterial(mesh)
        )
        ground.position.y = -0.33 * envVars.focus
        ground.rotateX(-Math.PI / 2)
        ground.name = "ground"
        models.value.push({ name: 'ground', modelFile: ground })
    }

    for (let index = 0; index < clickable.list.length; index++) {
        loading_store.status = 'clickable'
        const element = clickable.list[index];
        const find_element = seekByName(scene.value, element.object_name)
        if (!find_element) continue
        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 point = pointer_pin.clone()
            point.rotateX(-0.5 * Math.PI)
            point.position.set(world_position.x, p * 3, world_position.z * 2)
            point.name = `${element.id}_clickable`
            point.scale.set(p * 2, p * 2, p * 2)
            point.updateMatrixWorld()

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

            sidebar_items.push({
                id: element.id,
                name: element.name
            })
        }
    }

    sidebar_scene.name = raw_data.name;
    sidebar_scene.setData(sidebar_items)

    loading_store.status = 'boxes'
    const box = new Box3();
    models.value.forEach(element => {
        if (element.name !== 'ground') {
            box.expandByObject(element.modelFile.clone());
        }
    });
    const box_size = new Vector3();
    box.getSize(box_size)
    props.loaded_pan(
        new Vector3(box_size.x * 0.25, box_size.y * 0.25, box_size.z * 0.25),
        new Vector3(box_size.x * -0.25, box_size.y * -0.25, box_size.z * -0.25),
    )
    controls.value.enabled = true;
    props.loaded(false)

    timer.value = 10
    if (controls.value && (controls.value as any).autoRotate) {
        (controls.value as any).autoRotate = false;
    }
}
const lookAtCamera = (obj) => {
    if (!obj || !obj.children) {
        return
    }
    obj.children.forEach(element => {
        if (element && typeof element.lookAt == 'function') {
            element.lookAt(obj.position.x, obj.position.y * Math.PI * -0.5, camera.value?.position.z)
        }
    });
}
const { onAfterRender } = useLoop()
onAfterRender(() => {
    clickable_refs.value.map(el => {
        lookAtCamera(el.value[0])
    })
    if (controls.value) {
        if (timer.value == 0) {
            (controls.value as any).update();
        }
    }
})

let oldObj = [] as { uuid: string, color: Color }[]
const passShader = (obj: Mesh | Group) => {
    if (obj instanceof Mesh) {
        oldObj.push({ uuid: obj.uuid, color: (obj.material as MeshStandardMaterial).color });
        (obj.material as MeshStandardMaterial).color = new Color(1, 0, 0)
    } else if (obj instanceof Group) {
        for (let c in obj.children) {
            passShader(obj.children[c])
        }
    }
}
const openSidebar = (id: number) => {
    sidebar.open(id)
}

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

onMounted(() => {
    document.addEventListener('click', clickEvent)

    document.addEventListener('click', stopTimer)
    document.addEventListener('contextmenu', stopTimer)

    document.addEventListener('touchstart', stopTimer)
    document.addEventListener('touchend', stopTimer)
    document.addEventListener('touchmove', stopTimer)

    if (sidebar.is_open) {
        sidebar.close()
    }
})
onUnmounted(() => {
    document.removeEventListener('click', clickEvent)

    document.removeEventListener('click', stopTimer)
    document.removeEventListener('contextmenu', stopTimer)

    document.removeEventListener('touchstart', stopTimer)
    document.removeEventListener('touchend', stopTimer)
    document.removeEventListener('touchmove', stopTimer)
})
const pointer = reactive({ x: 0, y: 0 })
const stopTimer = () => {
    timer.value = 10;
    if (controls.value && (controls.value as any).autoRotate) {
        (controls.value as any).autoRotate = false;
    }
}
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.parent ? el.object.parent.name : el.object.name) ?? false).filter(Boolean)
    if (names.length) {
        openSidebar(parseInt(names[0].replace('_clickable', '')))
    }
}
</script>
<template>
    <TresGroup name="loaded">
        <Env v-bind="envVars" />
        <!-- <PostProcessing /> -->
        <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">
            <TresObject3D v-bind="item.clone()" :ref="clickable_refs[i]" />
        </template>
    </TresGroup>
</template>