import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';

import { LOCAL_STORAGE_VERSIONS, LocalStorageKey } from '@/constants/localStorage';
import {
  type Notification,
  type NotificationDisplayData,
  type NotificationPreferences,
  type Notifications,
  NotificationStatus,
  NotificationType,
} from '@/constants/notifications';

import { useLocalStorage } from './useLocalStorage';
import { notificationTypes } from './useNotificationTypes';

import { renderSvgToDataUrl } from '../lib/renderSvgToDataUrl';

type NotificationsContextType = ReturnType<typeof useNotificationsContext>;

const NotificationsContext = createContext<NotificationsContextType>(
  {} as NotificationsContextType
);

NotificationsContext.displayName = 'Notifications';

export const NotificationsProvider = ({ ...props }) => (
  <NotificationsContext.Provider value={useNotificationsContext()} {...props} />
);

export const useNotifications = () => useContext(NotificationsContext)!;

const useNotificationsContext = () => {
  // Local storage
  // const [notifications, setNotifications] = useState<Notifications>({});
  const [notifications, setNotifications] = useLocalStorage<Notifications>({
    key: LocalStorageKey.Notifications,
    defaultValue: {},
  });

  const [notificationsLastUpdated, setNotificationsLastUpdated] = useLocalStorage<number>({
    key: LocalStorageKey.NotificationsLastUpdated,
    defaultValue: Date.now(),
  });

  const [notificationPreferences, setNotificationPreferences] =
    useLocalStorage<NotificationPreferences>({
      key: LocalStorageKey.NotificationPreferences,
      defaultValue: {
        [NotificationType.AbacusGenerated]: true,
        [NotificationType.SquidTransfer]: true,
        version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.NotificationPreferences],
      },
    });

  useEffect(() => {
    setNotificationsLastUpdated(Date.now());
  }, [notifications]);

  const getKey = useCallback(
    <T extends string | number>(notification: Pick<Notification<T>, 'type' | 'id'>) =>
      `${notification.type}/${notification.id}`,
    []
  );

  // Display data
  const [notificationsDisplayData, setNotificationsDisplayData] = useState(
    {} as Record<string, NotificationDisplayData>
  );

  const getDisplayData = useCallback(
    (notification: Notification) => notificationsDisplayData[getKey(notification)],
    [notificationsDisplayData]
  );

  // Check for version changes
  useEffect(() => {
    if (
      notificationPreferences.version !==
      LOCAL_STORAGE_VERSIONS[LocalStorageKey.NotificationPreferences]
    ) {
      setNotificationPreferences({
        [NotificationType.AbacusGenerated]: true,
        [NotificationType.SquidTransfer]: true,
        [NotificationType.ReleaseUpdates]: true,
        version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.NotificationPreferences],
      });
    }
  }, []);

  // Status changes
  const updateStatus = useCallback(
    (notification: Notification, status: NotificationStatus) => {
      notification.status = status;
      notification.timestamps[notification.status] = Date.now();
      setNotifications({ ...notifications, [getKey(notification)]: notification });
    },
    [notifications, getKey]
  );

  const { markUnseen, markSeen, markCleared } = useMemo(
    () => ({
      markUnseen: (notification: Notification) => {
        if (notification.status < NotificationStatus.Unseen) {
          updateStatus(notification, NotificationStatus.Unseen);
        }
      },
      markSeen: (notification: Notification) => {
        if (notification.status < NotificationStatus.Seen) {
          updateStatus(notification, NotificationStatus.Seen);
        }
      },
      markCleared: (notification: Notification) => {
        if (notification.status < NotificationStatus.Cleared) {
          updateStatus(notification, NotificationStatus.Cleared);
        }
      },
    }),
    [updateStatus]
  );

  const markAllCleared = useCallback(() => {
    for (const notification of Object.values(notifications)) {
      markCleared(notification);
    }
  }, [notifications, markCleared]);

  // Trigger
  for (const { type, useTrigger } of notificationTypes)
    useTrigger({
      trigger: useCallback(
        (id, displayData, updateKey, isNew = true) => {
          const key = getKey({ type, id });

          const notification = notifications[key];

          // Filter out notifications that are not enabled
          if (notificationPreferences[type] !== false) {
            // New unique key - create new notification
            if (!notification) {
              const notification = (notifications[key] = {
                id,
                type,
                timestamps: {},
                updateKey,
              } as Notification);
              updateStatus(
                notification,
                isNew ? NotificationStatus.Triggered : NotificationStatus.Cleared
              );
            } else if (JSON.stringify(updateKey) !== JSON.stringify(notification.updateKey)) {
              // updateKey changed - update existing notification

              const notification = notifications[key];

              notification.updateKey = updateKey;
              updateStatus(notification, NotificationStatus.Updated);
            }
          } else {
            // Notification is disabled - remove it
            delete notifications[key];
          }

          notificationsDisplayData[key] = displayData;
          setNotificationsDisplayData({ ...notificationsDisplayData });
        },
        [notifications, updateStatus, notificationPreferences[type]]
      ),

      lastUpdated: notificationsLastUpdated,
    });

  // Actions
  const actions = Object.fromEntries(
    notificationTypes.map(
      ({ type, useNotificationAction }) => [type, useNotificationAction?.()] as const
    )
  );

  const onNotificationAction = async (notification: Notification) =>
    await actions[notification.type]?.(notification.id);

  // Push notifications
  const [hasEnabledPush, setHasEnabledPush] = useLocalStorage({
    key: LocalStorageKey.PushNotificationsEnabled,
    defaultValue: Boolean(
      globalThis.Notification && globalThis.Notification.permission === 'granted'
    ),
  });
  const [isEnablingPush, setIsEnablingPush] = useState(false);

  const [pushNotificationsLastUpdated, setPushNotificationsLastUpdated] = useLocalStorage({
    key: LocalStorageKey.PushNotificationsLastUpdated,
    defaultValue: Date.now(),
  });

  const enablePush = async () => {
    if (globalThis.Notification) {
      setIsEnablingPush(true);
      setHasEnabledPush((await globalThis.Notification.requestPermission()) === 'granted');
      setIsEnablingPush(false);
    }
  };

  const disablePush = () => {
    setHasEnabledPush(false);
  };

  useEffect(() => {
    (async () => {
      if (!hasEnabledPush) return;

      for (const notification of Object.values(notifications))
        if (
          notification.status < NotificationStatus.Seen &&
          notification.timestamps[notification.status]! > pushNotificationsLastUpdated &&
          !globalThis.document.hasFocus()
        ) {
          const displayData = getDisplayData(notification);

          const iconUrl =
            displayData.icon && (await renderSvgToDataUrl(displayData.icon).catch(() => undefined));

          const pushNotification = new globalThis.Notification(displayData.title, {
            renotify: true,
            tag: getKey(notification),
            data: notification,
            description: displayData.body,
            icon: iconUrl ?? '/favicon.svg',
            badge: iconUrl ?? '/favicon.svg',
            image: iconUrl ?? '/favicon.svg',
            vibrate: displayData.toastSensitivity === 'foreground',
            requireInteraction: displayData.toastDuration === Infinity,
            // actions: [
            //   {
            //     action: displayData.actionDescription,
            //     title: displayData.actionDescription,
            //   }
            // ].slice(0, globalThis.Notification.maxActions),
          });

          pushNotification.addEventListener('click', () => {
            onNotificationAction(notification);
            markSeen(notification);
          });
        }

      setPushNotificationsLastUpdated(Date.now());
    })();
  }, [hasEnabledPush, notifications, markSeen]);

  // Menu state
  const [isMenuOpen, setIsMenuOpen] = useState(false);

  // Public
  return {
    notifications,
    /** Resolve key */
    getKey,
    /** Resolve associated NotificationDisplayData */
    getDisplayData,

    // Status changes
    markUnseen,
    markSeen,
    markCleared,
    markAllCleared,

    // Actions
    onNotificationAction,

    // Push notifications
    hasEnabledPush,
    isEnablingPush,
    enablePush,
    disablePush,

    // Menu state
    isMenuOpen,
    setIsMenuOpen,

    // Notification Preferences
    notificationPreferences,
    setNotificationPreferences,
    getNotificationPreferenceForType: useCallback(
      (type: NotificationType) => notificationPreferences[type],
      [notificationPreferences]
    ),
  };
};
