<template>
    <teleport :to="mount">
        <div class="ui-floating" :style="style" :class="props.class" @mouseenter="emit('enter')" @mouseleave="emit('leave')">
            <slot />
        </div>
    </teleport>
</template>

<script setup lang="ts">
// onUpdated
import { computed, defineExpose, defineProps, onActivated, onUnmounted, reactive, watch } from 'vue'
import { getPostionByTarget, getScrollParents } from '@kankan-components/utils'
import { useZIndex } from '@kankan-components/hooks'
const { currentZIndex } = useZIndex()

defineOptions({
    name: 'UIFloating',
})

const Horizontal = {
    center: 'center',
    right: 'right',
    left: 'left',
}
const Vertical = {
    center: 'center',
    top: 'top',
    bottom: 'bottom',
}
const Divide = '-'

const props = defineProps({
    mount: {
        require: true,
        default: document.body,
    },
    class: { type: String },
    refer: { type: Object },
    dire: { type: String },
    width: { type: [Number, String] },
    height: { type: [Number, String] },
})
const emit = defineEmits(['leave', 'enter'])

// 确定方向
const dires = computed(() => {
    const dire = props.dire || `${Vertical.bottom}${Divide}${Horizontal.left}`
    const isPreset = (preset, val) => Object.keys(preset).some(key => preset[key] === val)

    let [horizontal, vertical] = dire.split(Divide)

    if (!horizontal || !isPreset(Horizontal, horizontal)) {
        horizontal = Horizontal.left
    }
    if (!vertical || !isPreset(Vertical, vertical)) {
        vertical = Vertical.bottom
    }
    return [horizontal, vertical]
})

const normalizeUnit = (unit: number | string, total: number): number => {
    if (unit === 0) {
        return 0
    } else if (typeof unit === 'number') {
        return unit ? (unit <= 1 && unit >= 0 ? total * unit : unit) : 0
    } else if (unit.includes('px')) {
        return normalizeUnit(Number.parseFloat(unit), total)
    } else if (unit.includes('%')) {
        return normalizeUnit(Number.parseFloat(unit) / 100, total)
    }
    return 0
}

const width = computed(() => props.refer && normalizeUnit(props.width, props.refer.offsetWidth))
const height = computed(() => props.refer && normalizeUnit(props.height, props.refer.offsetHeight))

const location = reactive({ x: 0, y: 0 })
const scrollParents = computed(() => (props.refer ? getScrollParents(props.refer, props.mount) : []))

watch(
    [scrollParents, props],
    ([newParents], [oldParents]) => {
        oldParents && oldParents.forEach(dom => dom.removeEventListener('scroll', updateLocation))
        newParents.forEach(dom => dom.addEventListener('scroll', updateLocation))
        if (props.refer) {
            setTimeout(() => updateLocation())
        }
    },
    { immediate: true }
)

// const zIndex = currentZIndex

const style = computed(() => {
    const style = {
        width: width.value && `${width.value}px`,
        height: height.value && `${height.value}px`,
        left: `${location.x}px`,
        top: `${location.y}px`,
        zIndex: currentZIndex,
    }
    if (location.x > 0 && location.y > 0) {
        return style
    }
    return {}
})

const updateLocation = () => {
    const pos = getPostionByTarget(props.refer, props.mount)
    const screenInfo = scrollParents.value.reduce(
        (t, c) => {
            t.y += c.scrollTop
            t.x += c.scrollLeft
            return t
        },
        { x: 0, y: 0 }
    )

    const [horizontal, vertical] = dires.value
    const start = {
        x: pos.x - screenInfo.x,
        y: pos.y - screenInfo.y,
    }

    switch (horizontal) {
        case Horizontal.left:
            location.x = start.x
            break
        case Horizontal.right:
            location.x = start.x + pos.width
            break
        case Horizontal.center:
            location.x = start.x + pos.width / 2
            break
    }

    switch (vertical) {
        case Vertical.top:
            location.y = start.y
            break
        case Vertical.bottom:
            location.y = start.y + pos.height
            break
        case Vertical.center:
            location.y = start.y + pos.height / 2
            break
    }
}

window.addEventListener('resize', updateLocation)
onUnmounted(() => {
    scrollParents.value.forEach(dom => dom.removeEventListener('scroll', updateLocation))
    window.removeEventListener('resize', updateLocation)
})

onActivated(() => {
    if (props.refer) {
        updateLocation()
    }
})

defineExpose({
    updateLocation,
})
</script>
