<template>
  <view :style="`${rootStyle};display: inline-block;`">
    <view :class="`wd-sticky ${customClass}`" :style="stickyStyle" :id="styckyId">
      <view class="wd-sticky__container" :style="containerStyle">
        <wd-resize @resize="handleResize" custom-style="display: inline-block;">
          <slot />
        </wd-resize>
      </view>
    </view>
  </view>
</template>

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

<script lang="ts" setup>
import wdResize from '../wd-resize/wd-resize.vue'
import { computed, getCurrentInstance, reactive, ref, type CSSProperties } from 'vue'
import { addUnit, getRect, objToStyle, pause, uuid } from '../common/util'
import { stickyProps } from './types'
import { useParent } from '../composables/useParent'
import { STICKY_BOX_KEY } from '../wd-sticky-box/types'

const props = defineProps(stickyProps)
const styckyId = ref<string>(`wd-sticky${uuid()}`)
const observerList = ref<UniApp.IntersectionObserver[]>([])

const stickyState = reactive({
  position: 'absolute',
  boxLeaved: false,
  top: 0,
  height: 0,
  width: 0,
  state: ''
})

const { parent: stickyBox } = useParent(STICKY_BOX_KEY)

const { proxy } = getCurrentInstance() as any

const rootStyle = computed(() => {
  const style: CSSProperties = {
    'z-index': props.zIndex,
    height: addUnit(stickyState.height),
    width: addUnit(stickyState.width)
  }
  if (!stickyState.boxLeaved) {
    style['position'] = 'relative'
  }
  return `${objToStyle(style)};${props.customStyle}`
})

const stickyStyle = computed(() => {
  const style: CSSProperties = {
    'z-index': props.zIndex,
    height: addUnit(stickyState.height),
    width: addUnit(stickyState.width)
  }
  if (!stickyState.boxLeaved) {
    style['position'] = 'relative'
  }
  return `${objToStyle(style)};`
})

const containerStyle = computed(() => {
  const style: CSSProperties = {
    position: stickyState.position as 'static' | 'relative' | 'absolute' | 'sticky' | 'fixed',
    top: addUnit(stickyState.top)
  }
  return objToStyle(style)
})

const innerOffsetTop = computed(() => {
  let top: number = 0
  // #ifdef H5
  // H5端，导航栏为普通元素，需要将组件移动到导航栏的下边沿
  // H5的导航栏高度为44px
  top = 44
  // #endif

  return top + props.offsetTop
})

/**
 * 清除对当前组件的监听
 */
function clearObserver() {
  while (observerList.value.length !== 0) {
    observerList.value.pop()!.disconnect()
  }
}
/**
 * 添加对当前组件的监听
 */
function createObserver() {
  const observer = uni.createIntersectionObserver(proxy, { thresholds: [0, 0.5] })
  observerList.value.push(observer)
  return observer
}
/**
 *  当前内容高度发生变化时重置监听
 */
async function handleResize(detail: any) {
  stickyState.width = detail.width
  stickyState.height = detail.height
  await pause()
  observerContentScroll()
  if (!stickyBox || !stickyBox.observerForChild) return
  stickyBox.observerForChild(proxy)
}
/**
 *  监听吸顶元素滚动事件
 */
function observerContentScroll() {
  if (stickyState.height === 0 && stickyState.width === 0) return
  const offset = innerOffsetTop.value + stickyState.height
  clearObserver()
  createObserver()
    .relativeToViewport({
      top: -offset
    })
    .observe(`#${styckyId.value}`, (result) => {
      handleRelativeTo(result)
    })
  getRect(`#${styckyId.value}`, false, proxy).then((res) => {
    // #ifdef H5
    // H5端，查询节点信息未计算导航栏高度
    res.bottom = Number(res.bottom) + 44
    // #endif
    if (Number(res.bottom) <= offset) handleRelativeTo({ boundingClientRect: res })
  })
}
/**
 * @description 根据位置进行吸顶
 */
function handleRelativeTo({ boundingClientRect }: any) {
  // sticky 高度大于或等于 wd-sticky-box，使用 wd-sticky-box 无任何意义
  if (stickyBox && stickyState.height >= stickyBox.boxStyle.height) {
    stickyState.position = 'absolute'
    stickyState.top = 0
    return
  }

  let isStycky = boundingClientRect.top <= innerOffsetTop.value
  // #ifdef H5 || APP-PLUS
  isStycky = boundingClientRect.top < innerOffsetTop.value
  // #endif

  if (isStycky) {
    stickyState.state = 'sticky'
    stickyState.boxLeaved = false
    stickyState.position = 'fixed'
    stickyState.top = innerOffsetTop.value
  } else {
    stickyState.state = 'normal'
    stickyState.boxLeaved = false
    stickyState.position = 'absolute'
    stickyState.top = 0
  }
}

/**
 * 设置位置
 * @param setboxLeaved
 * @param setPosition
 * @param setTop
 */
function setPosition(boxLeaved: boolean, position: string, top: number) {
  stickyState.boxLeaved = boxLeaved
  stickyState.position = position
  stickyState.top = top
}

defineExpose({
  setPosition,
  stickyState,
  offsetTop: props.offsetTop
})
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>
