import { memo } from 'preact/compat';
import { useEffect, useRef, useState } from 'preact/hooks';
import { useHotkeys } from 'react-hotkeys-hook';

import { api } from '../utils/api';
import showToast from '../utils/show-toast';
import states, { saveStatus } from '../utils/states';
import useInterval from '../utils/useInterval';
import usePageVisibility from '../utils/usePageVisibility';

const STREAMING_TIMEOUT = 1000 * 3; // 3 seconds
const POLL_INTERVAL = 1000 * 60; // 1 minute

export default memo(function BackgroundService({ isLoggedIn }) {
  // Notifications service
  // - WebSocket to receive notifications when page is visible
  const [visible, setVisible] = useState(true);
  usePageVisibility(setVisible);
  const checkLatestNotification = async (masto, instance, skipCheckMarkers) => {
    if (states.notificationsLast) {
      const notificationsIterator = masto.v1.notifications.list({
        limit: 1,
        sinceId: states.notificationsLast.id,
      });
      const { value: notifications } = await notificationsIterator.next();
      if (notifications?.length) {
        if (skipCheckMarkers) {
          states.notificationsShowNew = true;
        } else {
          let lastReadId;
          try {
            const markers = await masto.v1.markers.fetch({
              timeline: 'notifications',
            });
            lastReadId = markers?.notifications?.lastReadId;
          } catch (e) {}
          if (lastReadId) {
            states.notificationsShowNew = notifications[0].id !== lastReadId;
          } else {
            states.notificationsShowNew = true;
          }
        }
      }
    }
  };

  useEffect(() => {
    let sub;
    let pollNotifications;
    if (isLoggedIn && visible) {
      const { masto, streaming, instance } = api();
      (async () => {
        // 1. Get the latest notification
        await checkLatestNotification(masto, instance);

        let hasStreaming = false;
        // 2. Start streaming
        if (streaming) {
          pollNotifications = setTimeout(() => {
            (async () => {
              try {
                hasStreaming = true;
                sub = streaming.user.notification.subscribe();
                console.log('🎏 Streaming notification', sub);
                for await (const entry of sub) {
                  if (!sub) break;
                  if (!visible) break;
                  console.log('🔔🔔 Notification entry', entry);
                  if (entry.event === 'notification') {
                    console.log('🔔🔔 Notification', entry);
                    saveStatus(entry.payload, instance, {
                      skipThreading: true,
                    });
                  }
                  states.notificationsShowNew = true;
                }
              } catch (e) {
                hasStreaming = false;
                console.error(e);
              }

              if (!hasStreaming) {
                console.log('🎏 Streaming failed, fallback to polling');
                pollNotifications = setInterval(() => {
                  checkLatestNotification(masto, instance, true);
                }, POLL_INTERVAL);
              }
            })();
          }, STREAMING_TIMEOUT);
        }
      })();
    }
    return () => {
      sub?.unsubscribe?.();
      sub = null;
      clearTimeout(pollNotifications);
      clearInterval(pollNotifications);
    };
  }, [visible, isLoggedIn]);

  // Check for updates service
  const lastCheckDate = useRef();
  const checkForUpdates = () => {
    lastCheckDate.current = Date.now();
    console.log('✨ Check app update');
    fetch('./version.json')
      .then((r) => r.json())
      .then((info) => {
        if (info) states.appVersion = info;
      })
      .catch((e) => {
        console.error(e);
      });
  };
  useInterval(checkForUpdates, visible && 1000 * 60 * 30); // 30 minutes
  usePageVisibility((visible) => {
    if (visible) {
      if (!lastCheckDate.current) {
        checkForUpdates();
      } else {
        const diff = Date.now() - lastCheckDate.current;
        if (diff > 1000 * 60 * 60) {
          // 1 hour
          checkForUpdates();
        }
      }
    }
  });

  // Global keyboard shortcuts "service"
  useHotkeys('shift+alt+k', () => {
    const currentCloakMode = states.settings.cloakMode;
    states.settings.cloakMode = !currentCloakMode;
    showToast({
      text: `Cloak mode ${currentCloakMode ? 'disabled' : 'enabled'}`,
    });
  });

  return null;
});
