import { createVNode, render } from 'vue'
import { defu } from 'defu'
import NotificationConstructor from './notification.vue'
import type {
  NotificationOptions,
  NotificationPlacement,
  NotificationQueue,
} from './types'

const notifications: Record<NotificationPlacement, NotificationQueue> = {
  'top-left': [],
  'top-right': [],
  'bottom-left': [],
  'bottom-right': [],
}

const GAP_SIZE = 16
let SEED = 1

const DEFAULT_OPTIONS: NotificationOptions = {
  text: '',
  placement: 'top-right',
  duration: 5000,
  onClose: () => {},
}

const notify = function (options: NotificationOptions, context = null) {
  // if (process.server) return { close: () => undefined };

  options = defu(options, DEFAULT_OPTIONS)

  const orientedNotifications = notifications[options.placement!]
  const id = options.id
    ? `${options.placement!}_${options.id}`
    : `notification_${SEED++}`

  const idx = orientedNotifications.findIndex(
    ({ vm }) => vm.component?.props.id === id,
  )
  if (idx > -1)
    return

  let verticalOffset = options.offset || 0
  notifications[options.placement!].forEach(({ vm }) => {
    verticalOffset += (vm.el?.offsetHeight || 0) + GAP_SIZE
  })
  verticalOffset += GAP_SIZE

  const userOnClose = options.onClose
  const props = {
    ...options,
    offset: verticalOffset,
    id,
    onClose: () => {
      close(id, options.placement!, userOnClose)
    },
  }

  const container = document.createElement('div')

  const vm = createVNode(
    NotificationConstructor,
    props,
    options.text
      ? {
          default: () => options.text,
        }
      : null,
  )
  vm.appContext = context ?? notify._context
  vm.props!.onDestroy = () => {
    render(null, container)
  }

  render(vm, container)
  notifications[options.placement!].push({ vm })
  document.body.appendChild(container.firstElementChild!)

  return {
    close: () => {
      vm.component!.exposed!.close()
    },
  }
}

export function close(
  id: NotificationOptions['id'],
  placement: NotificationOptions['placement'],
  userOnClose: NotificationOptions['onClose'],
) {
  const orientedNotifications = notifications[placement!]
  const idx = orientedNotifications.findIndex(
    ({ vm }) => vm.component?.props.id === id,
  )
  if (idx === -1)
    return

  const { vm } = orientedNotifications[idx]

  if (!vm)
    return

  userOnClose?.(vm)

  const removedHeight = vm.el!.offsetHeight
  const verticalPos = placement!.split('-')[0]
  orientedNotifications.splice(idx, 1)

  if (orientedNotifications.length < 1)
    return

  for (let i = idx; i < orientedNotifications.length; i++) {
    const { el, component } = orientedNotifications[i].vm
    const styles = getComputedStyle(el as Element)
    const pos = Number.parseInt(styles.getPropertyValue(verticalPos), 10)

    component!.props.offset = pos - removedHeight - GAP_SIZE
  }
}

export function closeAll() {
  for (const orientedNotifications of Object.values(notifications)) {
    orientedNotifications.forEach(({ vm }) => {
      vm.component!.exposed!.close()
    })
  }
}

notify.closeAll = closeAll
notify._context = null

export default notify
