/**
 * Copyright (c) 2022 - present TinyVue Authors.
 * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
 *
 * Use of this source code is governed by an MIT-style license.
 *
 * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
 * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
 * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
 *
 */

import {
  bindEvent,
  show,
  hide,
  handleFocus,
  handleBlur,
  removeFocusing,
  handleShowPopper,
  handleClosePopper,
  setExpectedState,
  destroyed,
  debounceClose,
  watchFocusing,
  bindPopper,
  focusHandler,
  observeCallback,
  handleDocumentClick
} from './index'
import { userPopper } from '@opentiny/vue-hooks'
import { guid } from '@opentiny/utils'
import type { ISharedRenderlessParamHooks, ISharedRenderlessParamUtils } from 'types/shared.type'
import type { ITooltipApi, ITooltipProps, ITooltipState } from 'types/tooltip.type'

export const api = [
  'state',
  'bindEvent',
  'hide',
  'show',
  'doDestroy',
  'handleFocus',
  'debounceClose',
  'handleShowPopper',
  'handleClosePopper',
  'setExpectedState',
  'updatePopper',
  'focusHandler'
]

const initState = ({ reactive, showPopper, popperElm, referenceElm, props, inject, popperJS, currentPlacement }) =>
  reactive({
    popperJS,
    showPopper,
    popperElm,
    referenceElm,
    currentPlacement,
    timeout: null,
    focusing: false,
    expectedState: undefined,
    tooltipId: guid('tiny-tooltip-', 4),
    tabindex: props.tabindex,
    xPlacement: 'bottom',
    showContent: inject('showContent', null),
    tipsMaxWidth: inject('tips-max-width', null)
  })

export const renderless = (
  props: ITooltipProps,
  {
    watch,
    toRefs,
    reactive,
    onBeforeUnmount,
    onDeactivated,
    onMounted,
    onUnmounted,
    inject
  }: ISharedRenderlessParamHooks,
  { vm, emit, slots, nextTick, parent }: ISharedRenderlessParamUtils<never>
) => {
  const api = {} as ITooltipApi

  const popperVmRef = {} as { popper: HTMLElement }
  const popperParam = { emit, props, nextTick, toRefs, reactive, parent: parent.$parent, vm, popperVmRef }

  Object.assign(popperParam, { slots, onBeforeUnmount, onDeactivated, watch })

  const { showPopper, updatePopper, popperElm, referenceElm, doDestroy, popperJS, currentPlacement } = userPopper(
    popperParam as any
  )
  const state: ITooltipState = initState({
    reactive,
    showPopper,
    popperElm,
    referenceElm,
    props,
    inject,
    popperJS,
    currentPlacement
  })

  Object.assign(api, {
    state,
    doDestroy,
    updatePopper,
    show: show({ api, state, props }),
    hide: hide(api),
    destroyed: destroyed({ state, api, vm }),
    bindPopper: bindPopper({ vm, nextTick, popperVmRef }),
    watchFocusing: watchFocusing(state),
    removeFocusing: removeFocusing({ api, state }),
    handleBlur: handleBlur({ api, state }),
    handleFocus: handleFocus({ api, state }),
    debounceClose: debounceClose({ api, props }),
    setExpectedState: setExpectedState({ state }),
    handleShowPopper: handleShowPopper({ props, state }),
    handleClosePopper: handleClosePopper({ api, props, state }),
    bindEvent: bindEvent({ api, state, vm }),
    focusHandler: focusHandler({ slots, api }),
    handleDocumentClick: handleDocumentClick({ props, api, state, popperVmRef }),
    observeCallback: observeCallback({ state, popperVmRef })
  })

  watch(() => state.focusing, api.watchFocusing)

  watch(
    () => props.modelValue,
    (val) => nextTick(() => props.manual && (state.showPopper = val))
  )

  onMounted(() => {
    api.bindPopper()
    if (props.genArrowByHtml) {
      const config = { attributes: true, childList: false, subtree: false }
      api.observer = new MutationObserver(api.observeCallback)
      api.observer.observe(popperVmRef.popper, config)
    }
  })

  vm.$on('tooltip-update', api.bindPopper)

  onUnmounted(() => {
    api.destroyed()
    api.observer && api.observer.disconnect()
    vm.$off('tooltip-update')
  })

  return api
}