<script setup lang="ts">
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
import {
    Box3, Color, Group, Mesh,
    MeshStandardMaterial, MeshBasicMaterial,
    Vector2, Vector3,
    Quaternion, AdditiveBlending,
    Euler, SRGBColorSpace, RingGeometry,
    CircleGeometry,
} from 'three';

import { useTresContext, useSeek, useTexture, useLoop } from '@tresjs/core';
// @ts-ignore
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';
import { useTimer } from '../../stores/timer';
import { useRawData } from '../../stores/raw_data';
import { useTimerHome } from '../../stores/timer_home';
import { useItem } from '../../stores/item';
import { hsl } from 'd3';

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

const models = ref<model3DType[]>([])
const clickable_items = ref<any[]>([])
const clickable_refs = ref<any[]>([])
const envVars = reactive({}) as EnvVars
const process_loading = ref(null)
const targetDistance = reactive({ max: 10, min: 0 })
let sidebar_clickable = [] as PromoScene[]
let sidebar_visible = [] as PromoScene[]

const COUNT = 50
type smooth = {
    value: Vector3 | Quaternion | undefined,
    count: number
}
const smooth_target = reactive({}) as smooth
const smooth_move = reactive({}) as smooth
const smooth_rotate = reactive({}) as smooth
const temp_smooth_target = reactive({}) as smooth
const temp_smooth_move = reactive({}) as smooth
const temp_smooth_rotate = reactive({}) as smooth

const sidebar = usePromoSidebar();
const sidebar_scene = usePromoScene();
const clickable = useClickable()
const loading_store = useLoading()
const raw_dataStore = useRawData();
const itemStore = useItem();

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

const { scene: point_mesh_obj } = await useGLTF((await import('../../assets/pointer/point-v1-v1.glb')).default)

const set_moveto = (obj: smooth, value: Vector3 | Quaternion | undefined) => {
    obj.value = value
    obj.count = COUNT
}

const setEnv = async () => {
    envVars.focus = raw_dataStore.data.max_distance * 0.5
    if (raw_dataStore.data.env) {
        Object.assign(envVars, raw_dataStore.data.env)
    } else {
        envVars.clear_color = PROMOBG
    }
}

const setControls = () => {
    if (!controls.value) return;
    controls.value.enabled = false;
    targetDistance.max = raw_dataStore.data.max_distance;
    targetDistance.min = mobileAndTabletCheck() ? raw_dataStore.data.min_distance * 0.5 : raw_dataStore.data.min_distance;
    // (controls.value as any).minDistance = mobileAndTabletCheck() ? raw_dataStore.data.min_distance * 0.5 : raw_dataStore.data.min_distance;
    (controls.value as any).maxDistance = raw_dataStore.data.max_distance;

    const d = targetDistance.max * 0.5
    camera.value?.position.set(d, d, d);
    (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 clearValues = () => {
    clickable_items.value = []
    clickable_refs.value = []
    sidebar_clickable = []
    sidebar_visible = []
}

const loadModels = async () => {
    if (!props.source) return
    if (!raw_dataStore.data) return

    console.log(`load models ${props.source} ${process_loading.value}`)
    clearValues()

    loading_store.status = 'loading'
    process_loading.value = props.source
    await raw_dataStore.load(props)
    raw_dataStore.data.loading = true

    loading_store.status = 'env'
    await setEnv()

    loading_store.status = 'other'
    setControls()

    sidebar_scene.setName({ name: raw_dataStore.data.name, description: raw_dataStore.data.name })

    loading_store.status = 'model'
    for (let index = 0; index < raw_dataStore.data.elements.length; index++) {
        if (process_loading.value !== props.source) return
        loading_store.count = index
        const element = raw_dataStore.data.elements[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.id = element.id
        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
            )
            item.modelFile.children[0].updateMatrixWorld(true)
        }

        item.modelFile.traverse((el: Mesh) => {
            if (el.material && el.material instanceof MeshStandardMaterial && el.material.map) {
                el.material.map.colorSpace = SRGBColorSpace
            }
        })
        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)

        if (!element.can_not_disable) {
            sidebar_visible.push(element)
        }
    }
    sidebar_scene.setVisible(sidebar_visible)

    if (!models.value.find(el => el.name == 'ground')) {
        loading_store.status = '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();
            const size = new Vector3()
            const boundingBox = ((find_element as Mesh).geometry.boundingBox as any)
            boundingBox.getCenter(world_position);
            boundingBox.getCenter(size);
            (find_element as Mesh).localToWorld(world_position);

            const point = point_mesh_obj.clone()
            const point_color = new Color(envVars.clear_color).offsetHSL(0.45, 0.2, 0)
            point.traverse((el: Group | Mesh) => {
                if (el instanceof Mesh && el.material && el.material instanceof MeshStandardMaterial) {
                    el.position.set(0, 0, 0)
                    el.material.color = point_color
                }
            })
            point.position.set(world_position.x, world_position.y, world_position.z)
            console.log(world_position)

            point.updateMatrixWorld()
            point.name = `${element.id}_clickable`

            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_clickable.push({
                is_enabled: true,
                id: element.id,
                name: element.name
            })
        }
    }

    sidebar_scene.setClickable(sidebar_clickable)

    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.5, box_size.y * 0.5, box_size.z * 0.5),
        new Vector3(box_size.x * -0.25, box_size.y * -0.25, box_size.z * -0.25),
    );
    (controls.value as any).enabled = true;
    raw_dataStore.data.loading = false;

    timer.startTimer()
    if (controls.value && (controls.value as any).autoRotate) {
        (controls.value as any).autoRotate = false;
    }

    // process_loading.value = null
}

const gotoCenterAndDistance = () => {
    targetDistance.min = mobileAndTabletCheck() ? raw_dataStore.data.min_distance * 0.5 : raw_dataStore.data.min_distance;
    targetDistance.max = raw_dataStore.data.max_distance;
    set_moveto(smooth_target, new Vector3(0, 0, 0))
    set_moveto(smooth_move, new Vector3(
        raw_dataStore.data.max_distance * 0.5,
        raw_dataStore.data.max_distance * 0.5,
        raw_dataStore.data.max_distance * 0.5
    ))
}

watch(() => props.source, () => {
    if (props.source) {
        raw_dataStore.$reset()
        const loaded = seekByName(scene.value, 'loaded')
        if (loaded) {
            loaded.children = []
        }
        console.log('props change')
        sidebar.close()
        // loadModels()
    } else {
        renderer.value.dispose()
    }
}, { deep: true })

watch(() => sidebar.is_open, () => {
    if (sidebar.is_open == false) {
        gotoCenterAndDistance();
    }
})
watch(() => sidebar.is_open && sidebar.id_clickable, () => {
    if (sidebar.is_open && sidebar.id_clickable) {
        const clickable = useClickable()
        const target = clickable.list.find(el => el.id == sidebar.id_clickable)
        if (!target) return
        const el = seekByName(scene.value, `${sidebar.id_clickable}_clickable`);
        if (el) {
            targetDistance.max = 10
            targetDistance.min = 1
            const target_vector = new Vector3();

            el.getWorldPosition(target_vector);
            // target_vector.y = raw_dataStore.data.min_distance > 50 ? raw_dataStore.data.min_distance / 10 : 1;

            const quaternion = new Quaternion();
            quaternion.setFromEuler(new Euler(
                -45 * 1 * (Math.PI / 180),
                35 * 1 * (Math.PI / 180),
                35 * 1 * (Math.PI / 180)
            ));

            set_moveto(smooth_rotate, quaternion)

            const d = raw_dataStore.data.max_distance * 0.5
            if (camera.value?.position && camera.value?.position.distanceTo(new Vector3(d, d, d)) > 20) {
                set_moveto(temp_smooth_target, target_vector)
                set_moveto(temp_smooth_move, target_vector)

                set_moveto(smooth_target, new Vector3(0, 0, 0))
                set_moveto(smooth_move, new Vector3(d, d, d))
            } else {
                set_moveto(smooth_target, target_vector)
                set_moveto(smooth_move, target_vector)
            }
        }
    }
}, { deep: true })

const { onAfterRender } = useLoop()
onAfterRender(() => {
    clickable_refs.value.map(el => {
        if (el.value[0] && el.value[0].children) {
            el.value[0].children[0].lookAt(camera.value?.position);
            el.value[0].children[0].rotateX(90 * (Math.PI / 180));
            el.value[0].children[0].rotateZ(15 * (Math.PI / 180));

            const dis_to_cam = camera.value?.position.distanceTo(el.value[0].position);
            if (dis_to_cam) {
                const scaling = (0.5 * dis_to_cam) / 100
                el.value[0].children[0].scale.set(scaling, scaling, scaling);
                el.value[0].updateMatrixWorld()
            }
        }
    })
    const koef = (1 / COUNT) * 3
    const smoothy = [
        {
            el: smooth_target,
            temp_el: temp_smooth_target,
            f: () => (controls.value as any).target.lerp(smooth_target.value as Vector3, smooth_target.count < COUNT * 0.5 ? 0.15 : koef),
        },
        {
            el: smooth_move,
            temp_el: temp_smooth_move,
            f: () => camera.value?.position.lerp(smooth_move.value as Vector3, smooth_move.count < COUNT * 0.5 ? 0.15 : koef),
        },
        {
            el: smooth_rotate,
            f: () => {
                camera.value?.quaternion.slerp(smooth_rotate.value as Quaternion, koef);
                camera.value?.quaternion.normalize()
            }
        }
    ]
    smoothy.forEach(element => {
        if (element.el.value) {
            timer.resetTimer();
            element.f()
            element.el.count -= 1
            if (element.el.count == 0) {
                set_moveto(element.el, undefined)

                if (element.temp_el && element.temp_el.value) {
                    set_moveto(element.el, element.temp_el.value)
                    set_moveto(element.temp_el, undefined)
                }
            }
        }
    });
})

const timer = useTimer()
timer.timer_func = () => {
    if (timer.seconds_left == 0 && !(controls.value as any).autoRotate && (controls.value as any).enabled) {
        gotoCenterAndDistance();
        sidebar.close();
        (controls.value as any).autoRotate = true;
        (controls.value as any).autoRotateSpeed = 0.5;

        timer.startTimer()
        home_timer.startTimer()
    }
}

const stopTimer = () => {
    timer.resetTimer()
    home_timer.stopTimer()
    if ((controls.value as any).autoRotate) {
        (controls.value as any).autoRotate = false
    }
}
const home_timer = useTimerHome()
home_timer.timer_func = () => {
    if (itemStore.page.scene_3d !== raw_dataStore.data.id) {
        props.push(`/${itemStore.page.slug}/${itemStore.page.scene_3d}`)
    }
}

const pointer = reactive({ x: 0, y: 0 })
const clickEvent = (event: MouseEvent) => {
    if (event.target && !(event.target as HTMLElement).closest('canvas')) return

    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 clickable_objects = seekAllByName(scene.value, '_clickable');
    const intersects = raycaster.value.intersectObjects(clickable_objects);
    const names = intersects
        .map(el => (el.object.parent ? el.object.parent.name : el.object.name) ?? false)
        .filter(Boolean)
    if (names.length) {
        sidebar.open(parseInt(names[0].replace('_clickable', '')))
        sidebar.toggleAccordion('clickable')
    }
}
const timerEvent = ['click', 'contextmenu', 'mousedown', 'mouseup', 'touchstart', 'touchend', 'touchmove']
onMounted(() => {
    console.log('mount')
    clearValues()
    loadModels()

    document.addEventListener('click', clickEvent)

    timerEvent.map((event: string) => {
        document.addEventListener(event, stopTimer)
    })

    if (sidebar.is_open) {
        sidebar.close()
    }
})
onUnmounted(() => {
    console.log('unmount load models')
    clearValues()
    document.removeEventListener('click', clickEvent)
    timerEvent.map((event: string) => {
        document.removeEventListener(event, stopTimer)
    })
    renderer.value.dispose()
})
</script>
<template>
    <TresGroup name="loaded" :key="props.source" ref="loaded">
        <Env v-bind="envVars" />
        <!-- <PostProcessing /> -->
        <template v-for="item in models">
            <TresGroup :name="item.name"
                :visible="sidebar_scene.visible.find(el => el.id == item.id)?.is_enabled ?? true">
                <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>