<script lang="ts" setup>
import type { CSSProperties, Ref } from 'vue'
import { computed, defineComponent, inject, onBeforeUnmount, ref, useSlots, watch } from 'vue'
import { CLICK_EVENT, CLOSE_EVENT, CLOSED_EVENT, PREFIX, UPDATE_VISIBLE_EVENT } from '../_constants'
import { cloneDeep, getMainClass, getMainStyle, pxCheck } from '../_utils'
import NutPopup from '../popup/popup.vue'
import { notifyDefaultOptions, notifyDefaultOptionsKey, notifyEmits, notifyProps } from './notify'
import type { NotifyOptions, NotifyType } from './types'

const props = defineProps(notifyProps)

const emit = defineEmits(notifyEmits)

const slots = useSlots()

const notifyOptionsKey = `${notifyDefaultOptionsKey}${props.selector || ''}`
const injectNotifyOptions: Ref<NotifyOptions> = inject(notifyOptionsKey, ref(cloneDeep(notifyDefaultOptions)))

const innerVisible = ref(false)

const notifyOptions = ref<NotifyOptions>(cloneDeep(props))

const classes = computed(() => {
  const { type, className } = notifyOptions.value

  const value = {
    [`nut-notify--${type}`]: true,
  }

  if (className) {
    value[className] = true
  }

  return getMainClass(props, componentName, value)
})

const styles = computed(() => {
  const value: CSSProperties = {}

  const { customColor, background } = notifyOptions.value

  if (customColor) {
    value.color = customColor
  }

  if (background) {
    value.background = background
  }

  return getMainStyle(props, value)
})

const wrapperStyles = computed(() => {
  const value: CSSProperties = {}

  const { position, safeAreaInsetTop, safeAreaInsetBottom, safeHeight } = notifyOptions.value

  if (position === 'top') {
    if (safeAreaInsetTop) {
      if (safeHeight) {
        value.top = pxCheck(safeHeight)
      }
      else {
        value.top = `${uni.getSystemInfoSync().statusBarHeight}px`
      }
    }
  }
  else if (position === 'bottom') {
    if (safeAreaInsetBottom) {
      if (safeHeight) {
        value.bottom = pxCheck(safeHeight)
      }
    }
  }

  return value
})

let timer = 0

function startTimer() {
  // @ts-expect-error whatever
  timer = setTimeout(() => {
    hide()
  }, notifyOptions.value.duration)
}

function destroyTimer() {
  if (timer === 0)
    return

  clearTimeout(timer)
  timer = 0
}

function show(type: NotifyType, msg: string, options?: NotifyOptions) {
  destroyTimer()

  notifyOptions.value = Object.assign(cloneDeep(notifyDefaultOptions), {
    visible: true,
    type,
    msg,
  }, options)

  innerVisible.value = true

  if (notifyOptions.value.duration! > 0)
    startTimer()
}

function legacyShow(options: NotifyOptions) {
  show(notifyDefaultOptions.type, options.msg || notifyDefaultOptions.msg, options)
}

function showPrimary(msg: string, options?: NotifyOptions) {
  show('primary', msg, options)
}

function showSuccess(msg: string, options?: NotifyOptions) {
  show('success', msg, options)
}

function showDanger(msg: string, options?: NotifyOptions) {
  show('danger', msg, options)
}

function showWarning(msg: string, options?: NotifyOptions) {
  show('warning', msg, options)
}

function showCustom(msg: string, options?: NotifyOptions) {
  show('custom', msg, options)
}

function hide() {
  destroyTimer()

  innerVisible.value = false
  notifyOptions.value.visible = false

  emit(UPDATE_VISIBLE_EVENT, false)
  emit(CLOSE_EVENT)

  if (notifyOptions.value.onClose) {
    notifyOptions.value.onClose()
  }
}

function handleClosed() {
  emit(CLOSED_EVENT)

  if (notifyOptions.value.onClosed) {
    notifyOptions.value.onClosed()
  }
}

function handleClick() {
  emit(CLICK_EVENT)

  if (notifyOptions.value.onClick) {
    notifyOptions.value.onClick()
  }
}

watch(() => props, (value) => {
  notifyOptions.value = Object.assign(cloneDeep(notifyDefaultOptions), value)

  if (value.visible)
    show(notifyOptions.value.type!, notifyOptions.value.msg!, notifyOptions.value)
  else
    hide()
}, { deep: true })

watch(injectNotifyOptions, (value) => {
  notifyOptions.value = Object.assign(cloneDeep(notifyDefaultOptions), value)

  if (value.visible)
    show(notifyOptions.value.type!, notifyOptions.value.msg!, notifyOptions.value)
  else
    hide()
})

onBeforeUnmount(() => {
  destroyTimer()
})

defineExpose({
  showNotify: legacyShow,
  hideNotify: hide,

  show,
  primary: showPrimary,
  success: showSuccess,
  danger: showDanger,
  warning: showWarning,
  custom: showCustom,
  hide,
})
</script>

<script lang="ts">
const componentName = `${PREFIX}-notify`

export default defineComponent({
  name: componentName,
  options: {
    virtualHost: true,
    addGlobalClass: true,
    styleIsolation: 'shared',
  },
})
</script>

<template>
  <NutPopup
    v-model:visible="innerVisible"
    :custom-style="wrapperStyles"
    :position="notifyOptions.position"
    :overlay="false"
    :z-index="notifyOptions.zIndex"
    :safe-area-inset-top="false"
    :safe-area-inset-bottom="false"
    @closed="handleClosed"
  >
    <view
      :class="classes"
      :style="styles"
      @click="handleClick"
    >
      <slot v-if="slots.default" />

      <template v-else>
        {{ notifyOptions.msg }}
      </template>
    </view>
  </NutPopup>
</template>

<style lang="scss">
@import "./index";
</style>
