import { cloneVNode, computed, defineComponent, h, onScopeDispose, PropType, ref, watch } from 'vue'
import { createPopper } from '@popperjs/core'

import { CConditionalTeleport } from '../conditional-teleport'
import { CFocusTrap } from '../focus-trap'

import { isRTL } from '../../utils'

const CPicker = defineComponent({
  name: 'CPicker',
  props: {
    /**
     * Set container type for the component, or the element the dropdown is rendered into.
     *
     * - `'dropdown'` - the component is rendered as a dropdown (default),
     * - `'inline'` - the component is rendered inline, without a dropdown,
     * - an HTML element, a CSS selector, or a function that returns a single element - the
     *   dropdown is rendered inside that element. Implies `teleport`. **[Added in v5.21.0]**
     */
    container: {
      type: [String, Object, Function] as PropType<
        'dropdown' | 'inline' | string | HTMLElement | (() => HTMLElement) | null
      >,
      default: 'dropdown',
    },
    /**
     * Toggle the disabled state for the component.
     */
    disabled: Boolean,
    /**
     * A string of all className you want applied to the dropdown menu.
     */
    dropdownClassNames: String,
    /**
     * Toggle visibility of footer element or set the content of footer.
     */
    footer: Boolean,
    /**
     * Generates dropdown menu using Teleport.
     *
     * @since 5.8.0
     */
    teleport: {
      type: [Boolean],
      default: false,
    },
    /**
     * Toggle the visibility of the component.
     */
    visible: Boolean,
  },
  emits: [
    /**
     * Callback fired when the component requests to be hidden.
     */
    'hide',
    /**
     * Callback fired when the component requests to be shown.
     */
    'show',
  ],
  setup(props, { attrs, emit, slots }) {
    const pickerRef = ref()
    const dropdownRef = ref()
    const togglerRef = ref()

    const popper = ref()
    const visible = ref(props.visible)

    const teleportContainer = computed(() =>
      !props.container || props.container === 'dropdown' || props.container === 'inline'
        ? undefined
        : props.container
    )
    const teleport = computed(() => props.teleport || Boolean(teleportContainer.value))

    watch(
      () => props.visible,
      () => {
        visible.value = props.visible
      }
    )

    watch(visible, () => {
      if (props.container === 'inline') {
        return
      }

      if (visible.value) {
        emit('show')
        window.addEventListener('mouseup', handleMouseUp)
        window.addEventListener('keyup', handleKeyUp)
        initPopper()

        return
      }

      emit('hide')
      window.removeEventListener('mouseup', handleMouseUp)
      window.removeEventListener('keyup', handleKeyUp)

      destroyPopper()
    })

    const initPopper = () => {
      if (togglerRef.value && dropdownRef.value) {
        popper.value = createPopper(togglerRef.value, dropdownRef.value, {
          placement: isRTL(pickerRef.value) ? 'bottom-end' : 'bottom-start',
          modifiers: [
            {
              name: 'preventOverflow',
              options: {
                boundary: 'clippingParents',
              },
            },
            {
              name: 'offset',
              options: {
                offset: [0, 2],
              },
            },
          ],
        })
      }
    }

    const destroyPopper = () => {
      if (popper.value) {
        popper.value.destroy()
      }

      popper.value = undefined
    }

    const handleKeyUp = (event: KeyboardEvent) => {
      if (event.key === 'Escape') {
        visible.value = false
      }
    }

    const handleMouseUp = (event: Event) => {
      if (pickerRef.value && pickerRef.value.contains(event.target as HTMLElement)) {
        return
      }

      if (dropdownRef.value && dropdownRef.value.contains(event.target as HTMLElement)) {
        return
      }

      visible.value = false
    }

    onScopeDispose(() => {
      window.removeEventListener('mouseup', handleMouseUp)
      window.removeEventListener('keyup', handleKeyUp)
    })

    switch (props.container) {
      case 'inline': {
        return () => h('div', { class: 'picker', ref: pickerRef }, slots.default && slots.default())
      }
      default: {
        return () =>
          h(
            CFocusTrap,
            { active: visible.value, ...(teleport.value && { additionalContainer: dropdownRef }) },
            () =>
              h(
                'div',
                {
                  class: [
                    attrs.class,
                    {
                      show: visible.value,
                    },
                  ],
                  ref: pickerRef,
                },
                [
                  /**
                   * @slot Location for the toggler element.
                   */
                  slots.toggler &&
                    slots.toggler().map((slot) =>
                      cloneVNode(slot, {
                        onClick: () => {
                          if (!props.disabled && !visible.value) {
                            visible.value = true
                          }
                        },
                        ref: (el) => {
                          togglerRef.value = el
                        },
                      })
                    ),
                  h(
                    CConditionalTeleport,
                    {
                      container: teleportContainer.value ?? 'body',
                      teleport: teleport.value,
                    },
                    {
                      default: () =>
                        h(
                          'div',
                          {
                            class: [
                              props.dropdownClassNames,
                              {
                                show: teleport.value && visible.value,
                              },
                            ],
                            ref: dropdownRef,
                          },
                          [
                            slots.default && slots.default(),
                            /**
                             * @slot Location for the footer element.
                             */
                            props.footer && slots.footer && slots.footer(),
                          ]
                        ),
                    }
                  ),
                ]
              )
          )
      }
    }
  },
})
export { CPicker }
