<template>
  <view style="position: relative">
    <view :class="`wd-sticky-box ${props.customClass}`" :style="customStyle" :id="styckyBoxId">
      <wd-resize @resize="handleResize">
        <slot />
      </wd-resize>
    </view>
  </view>
</template>

<script lang="ts">
export default {
  name: 'wd-sticky-box',
  options: {
    addGlobalClass: true,
    // virtualHost: true,
    styleIsolation: 'shared'
  }
}
</script>

<script lang="ts" setup>
import wdResize from '../wd-resize/wd-resize.vue'
import { getCurrentInstance, onBeforeMount, reactive, ref } from 'vue'
import { getRect, uuid } from '../common/util'
import { baseProps } from '../common/props'
import { STICKY_BOX_KEY } from './types'
import { useChildren } from '../composables/useChildren'

const props = defineProps(baseProps)

const styckyBoxId = ref<string>(`wd-sticky-box${uuid()}`)

const observerMap = ref<Map<any, any>>(new Map())

const boxStyle = reactive({
  height: 0,
  width: 0
})

const { proxy } = getCurrentInstance() as any

const { children: stickyList, linkChildren } = useChildren(STICKY_BOX_KEY)
linkChildren({
  boxStyle: boxStyle,
  observerForChild
})

onBeforeMount(() => {
  observerMap.value = new Map()
})

/**
 * 容器大小变化后重新监听sticky组件与box组件的交叉状态
 * @param detail
 */
function handleResize(detail: any) {
  // 相对的容器大小改变后，同步设置 wd-sticky-box 的大小
  boxStyle.width = detail.width
  boxStyle.height = detail.height
  // wd-sticky-box 大小变化时，重新监听所有吸顶元素
  const temp = observerMap.value
  observerMap.value = new Map()
  for (const [uid] of temp) {
    const child = stickyList.find((sticky) => {
      return sticky.$.uid === uid
    })
    observerForChild(child)
  }
  temp.forEach((observer) => {
    observer.disconnect()
  })
  temp.clear()
}
/**
 * 删除对指定sticky的监听
 * @param child 指定的子组件
 */
function deleteObserver(child: any) {
  const observer = observerMap.value.get(child.$.uid)
  if (!observer) return
  observer.disconnect()
  observerMap.value.delete(child.$.uid)
}
/**
 * 针对指定sticky添加监听
 * @param child 指定的子组件
 */
function createObserver(child: any) {
  const observer = uni.createIntersectionObserver(proxy, { thresholds: [0, 0.5] })
  observerMap.value.set(child.$.uid, observer)
  return observer
}
/**
 * 监听子组件
 * @param child 子组件
 */
function observerForChild(child: any) {
  deleteObserver(child)
  const observer = createObserver(child)
  const exposed = child.$.exposed
  let offset = exposed.stickyState.height + exposed.offsetTop
  // #ifdef H5
  // H5端，导航栏为普通元素，需要将组件移动到导航栏的下边沿
  // H5的导航栏高度为44px
  offset = offset + 44
  // #endif

  if (boxStyle.height <= exposed.stickyState.height) {
    exposed.setPosition(false, 'absolute', 0)
  }
  observer.relativeToViewport({ top: -offset }).observe(`#${styckyBoxId.value}`, (result) => {
    handleRelativeTo(exposed, result)
  })
  // 当子组件默认处于边界外且永远不会进入边界内时，需要手动调用一次
  getRect(`#${styckyBoxId.value}`, false, proxy)
    .then((res) => {
      // #ifdef H5
      // H5端，查询节点信息未计算导航栏高度
      res.bottom = Number(res.bottom) + 44
      // #endif
      if (Number(res.bottom) <= offset) handleRelativeTo(exposed, { boundingClientRect: res })
    })
    .catch((res) => {
      console.log(res)
    })
}
/**
 *  监听容器组件
 * @param {Object} exposed wd-sticky实例暴露出的事件
 * @param {Object} boundingClientRect 边界信息
 */
function handleRelativeTo(exposed: any, { boundingClientRect }: any) {
  let childOffsetTop = exposed.offsetTop
  // #ifdef H5
  // H5端，导航栏为普通元素，需要将组件移动到导航栏的下边沿
  // H5的导航栏高度为44px
  childOffsetTop = childOffsetTop + 44
  // #endif
  const offset = exposed.stickyState.height + childOffsetTop
  let isAbsolute = boundingClientRect.bottom <= offset
  // #ifdef H5 || APP-PLUS
  isAbsolute = boundingClientRect.bottom < offset
  // #endif
  if (isAbsolute) {
    exposed.setPosition(true, 'absolute', boundingClientRect.height - exposed.stickyState.height)
  } else if (boundingClientRect.top <= offset && !isAbsolute) {
    if (exposed.stickyState.state === 'normal') return
    exposed.setPosition(false, 'fixed', childOffsetTop)
  }
}
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>
