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

import { useTresContext, useSeek, useRenderLoop, 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';

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

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 { controls, camera, scene, raycaster, renderer } = useTresContext()
const { pause, resume } = useLoop()
const { seekByName, seekAllByName } = useSeek()
const envVars = reactive({}) as {
    focus: number,
    hdr_gainmap?: string,
    hdr_json?: string,
    hdr_webp?: string,
    clear_color?: string,
    env_displacementmap?: string,
    env_normalmap?: string
}

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

const timer = ref(10)
let int: any;

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

const mobileAndTabletCheck = () => {
    let check = false;
    (function (a) { if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true; })(navigator.userAgent || navigator.vendor || window.opera);
    return check;
};

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

    envVars.focus = raw_data.max_distance * 0.75
    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
    }


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

    camera.value?.position.set(1, 1, 1);
    controls.value.enabled = false;
    // console.log(mobileAndTabletCheck() ? raw_data.min_distance * 0.5 : raw_data.min_distance);
    (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;
    (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 })
        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.value.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)
    }

    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 })

    sidebar_scene.setData(sidebar_items)

    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
        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);
            point.position.set(world_position.x, p * 3, world_position.z)
            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.25, box_size.z * -0.5),
        )
    }

    controls.value.enabled = true;
    props.loaded(false)

    clearInterval(int)
    timer.value = 10
    int = setInterval(() => {
        if (timer.value > 0) {
            timer.value -= 1
        } else if (timer.value == 0 && !controls.value.autoRotate) {
            pause()
            camera.value?.position.set(
                controls.value.minDistance * 0.5,
                controls.value.minDistance * 0.5,
                controls.value.minDistance
            );
            (controls.value as any).autoRotate = true;
            (controls.value as any).autoRotateSpeed = 1;
            resume()
        }
    }, 1000)
}

const { onAfterRender } = useLoop()
onAfterRender(() => {
    clickable_refs.value.map(el => {
        if (el.value[0] && typeof el.value[0].lookAt == 'function') {
            el.value[0].lookAt(camera.value?.position);
        }
    })
    if (controls.value) {
        if (timer.value == 0) {
            (controls.value as any).update();
        }
    }
})

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)
    document.addEventListener('click', stopTimer)
    document.addEventListener('touchstart', stopTimer)
    if (sidebar.is_open) {
        sidebar.close()
    }
})
onUnmounted(() => {
    document.removeEventListener('click', clickEvent)
    document.removeEventListener('click', stopTimer)
    document.removeEventListener('touchstart', stopTimer)
})
const pointer = reactive({ x: 0, y: 0 })
const stopTimer = () => {
    timer.value = 10;
    if (controls.value.autoRotate) {
        controls.value.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.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>