import type {
  BroadcastToastProps,
  NotificationProps,
  OmittedToastProps,
} from '@inject/shared/notification/typing'
import { makeVar, useReactiveVar } from '@inject/shared/utils/reactive'
import { round } from 'lodash'
import Keys from '../../localstorage/keys'
import toaster from '../toaster'
import { limitReactiveVar } from './limit'

const key = Keys.NOTIFICATIONS

// reactive update channel, on ping it does pong!
const bc = new BroadcastChannel('notification_channel')
bc.onmessage = e => {
  if (e.data === 'ping') {
    notificationsReactiveVar(getValue())
  }
}

// generates the toast message
export const generateToast = (notification: BroadcastToastProps) => {
  toaster.show({
    onDismiss(didTimeoutExpire) {
      if (!didTimeoutExpire) {
        removeNotify(notification.message, notification.timestamp)
      }
    },
    ...notification,
  })
}

// explicit getter
const getValue = () =>
  (JSON.parse(localStorage.getItem(key) || '[]') as NotificationProps[]).slice(
    -limitReactiveVar()
  )

// reactive change function
function change(value: NotificationProps[]) {
  localStorage.setItem(key, JSON.stringify(value.slice(-limitReactiveVar())))
  bc.postMessage('ping')
}

export const notifyNoncommmit = (
  msg: string,
  toastProps?: OmittedToastProps
) => {
  toaster.show({
    message: msg,
    intent: toastProps?.intent ?? 'success',
    ...toastProps,
  })
}

export const notify = async (
  msg: string,
  errorJSON: string,
  toastProps?: OmittedToastProps
) => {
  const tm = Date.now()
  const lowFidelityTimestamp = round(tm / 1000)
  const props = toastProps ?? {}
  const newNotification: Omit<NotificationProps, 'errorJSON'> = {
    intent: props.intent ?? 'success',
    msg,
    timestamp: lowFidelityTimestamp,
  }
  const newErrorJSON = {
    ...JSON.parse(errorJSON),
    '_location.pathname': window.location.pathname,
    _traceback: new Error().stack,
  }

  generateToast({
    message: msg,
    timestamp: tm,
    intent: props.intent ?? 'success',
    ...toastProps,
  } as BroadcastToastProps)

  navigator.locks.request('notify_handler', () => {
    const mod = [
      {
        ...newNotification,
        errorJSON: JSON.stringify(newErrorJSON),
      },
      ...getValue(),
    ].slice(-limitReactiveVar())
    notificationsReactiveVar(mod)
    change(mod)
  })
}

export const clearNotify = () => {
  navigator.locks.request('notify_handler', () => {
    notificationsReactiveVar([])
    change([])
  })
}

export const removeNotify = (msg: string, timestamp: number) => {
  navigator.locks.request('notify_handler', () => {
    const mod =
      getValue().filter(
        notification =>
          !(notification.timestamp === timestamp && notification.msg === msg)
      ) ?? []
    notificationsReactiveVar(mod)
    change(mod)
  })
}

export const notificationsReactiveVar = makeVar<NotificationProps[]>(getValue())

// reactivity is modified so-as to not push any changes to it, it has be a willful process
notificationsReactiveVar.onNextChange(function onNext() {
  notificationsReactiveVar.onNextChange(onNext)
})

export const useNotificationStorage = () =>
  useReactiveVar(notificationsReactiveVar)
