import { useAtomValue } from 'jotai';
import { useAtom, useSetAtom } from 'jotai';
import React, { ReactNode, useCallback, useEffect, useRef, useState, Fragment } from 'react';
import { useNavigate } from 'react-router-dom';
import { RoomEvent, RoomEventHandlerMap } from 'matrix-js-sdk';
import { roomToUnreadAtom, unreadEqual, unreadInfoToUnread } from '../../state/room/roomToUnread';
import LogoSVG from '../../../../public/res/svg/paarrot.svg';
import LogoUnreadSVG from '../../../../public/res/svg/paarrot-unread.svg';
import LogoHighlightSVG from '../../../../public/res/svg/paarrot-highlight.svg';
import { notificationPermission, setFavicon } from '../../utils/dom';
import { useSetting } from '../../state/hooks/settings';
import { EmojiStyle, settingsAtom } from '../../state/settings';
import { allInvitesAtom } from '../../state/room-list/inviteList';
import { usePreviousValue } from '../../hooks/usePreviousValue';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { getDirectRoomPath, getHomeRoomPath, getSpaceRoomPath, getInboxInvitesPath } from '../pathUtils';
import {
  getMemberDisplayName,
  getNotificationType,
  getUnreadInfo,
  isNotificationEvent,
  getOrphanParents,
  guessPerfectParent,
} from '../../utils/room';
import { NotificationType, UnreadInfo } from '../../../types/matrix/room';
import { getMxIdLocalPart, mxcUrlToHttp, getCanonicalAliasOrRoomId } from '../../utils/matrix';
import { mDirectAtom } from '../../state/mDirectList';
import { roomToParentsAtom } from '../../state/room/roomToParents';
import { useSelectedRoom } from '../../hooks/router/useSelectedRoom';
import { useInboxNotificationsSelected } from '../../hooks/router/useInbox';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { plainToEditorInput, toPlainText } from '../../components/editor';
import { ShareRoomPicker } from '../../features/ShareRoomPicker';
import {
  isTauri,
  isElectron,
  isCapacitorNative,
  sendNotification,
  setupNotificationTapListener,
} from '../../utils/tauri';
import { setPaarrotNavigate, initPaarrotAPI } from '../../paarrot-api';
import {
  startBackgroundSync,
  stopBackgroundSync,
  setAppForegroundState,
} from '../../utils/backgroundSync';
import {
  TUploadItem,
  roomIdToMsgDraftAtomFamily,
  roomIdToUploadItemsAtomFamily,
} from '../../state/room/roomInputDrafts';
import { encryptFile } from '../../utils/matrix';
import {
  AndroidSharePayload,
  clearPendingAndroidShare,
  getPendingAndroidShare,
  isAndroidShareSupported,
  listenForAndroidShares,
  materializeSharedFile,
} from '../../utils/androidShare';

/**
 * Applies the selected emoji style font to the document.
 * - System: Uses the native OS emoji font
 * - Apple: Uses Apple Color Emoji (bundled font)
 * - Twemoji: Uses Twitter's Twemoji font
 */
function EmojiStyleFeature() {
  const [emojiStyle] = useSetting(settingsAtom, 'emojiStyle');

  switch (emojiStyle) {
    case EmojiStyle.Apple:
      document.documentElement.style.setProperty('--font-emoji', 'AppleColorEmoji');
      break;
    case EmojiStyle.Twemoji:
      document.documentElement.style.setProperty('--font-emoji', 'Twemoji');
      break;
    case EmojiStyle.System:
    default:
      document.documentElement.style.setProperty('--font-emoji', 'SystemEmoji');
      break;
  }

  return null;
}

function PageZoomFeature() {
  const [pageZoom] = useSetting(settingsAtom, 'pageZoom');

  if (pageZoom === 100) {
    document.documentElement.style.removeProperty('font-size');
  } else {
    document.documentElement.style.setProperty('font-size', `calc(1em * ${pageZoom / 100})`);
  }

  return null;
}

function FaviconUpdater() {
  const roomToUnread = useAtomValue(roomToUnreadAtom);

  useEffect(() => {
    let notification = false;
    let highlight = false;
    roomToUnread.forEach((unread) => {
      if (unread.total > 0) {
        notification = true;
      }
      if (unread.highlight > 0) {
        highlight = true;
      }
    });

    if (notification) {
      setFavicon(highlight ? LogoHighlightSVG : LogoUnreadSVG);
    } else {
      setFavicon(LogoSVG);
    }
  }, [roomToUnread]);

  return null;
}

function InviteNotifications() {
  const invites = useAtomValue(allInvitesAtom);
  const perviousInviteLen = usePreviousValue(invites.length, 0);
  const mx = useMatrixClient();

  const navigate = useNavigate();
  const [showNotifications] = useSetting(settingsAtom, 'showNotifications');
  const [notificationSound] = useSetting(settingsAtom, 'isNotificationSounds');

  const notify = useCallback(
    (count: number) => {
      const body = `You have ${count} new invitation request.`;
      const invitesPath = getInboxInvitesPath();
      
      // Flash taskbar icon for desktop notifications (only visible when window is not focused)
      if (isElectron() && (window as any).electron?.window?.flashFrame) {
        (window as any).electron.window.flashFrame(true)
          .then((result: { success: boolean }) => {
            console.log('[InviteNotifications] flashFrame result:', result);
          })
          .catch((err: Error) => {
            console.error('[InviteNotifications] flashFrame error:', err);
          });
      }
      
      if ((isTauri() && !isElectron()) || isCapacitorNative()) {
        sendNotification({
          title: 'Invitation',
          body,
          path: invitesPath,
          onClick: () => {
            if (!window.closed) navigate(invitesPath);
          },
        });
      } else {
        const noti = new window.Notification('Invitation', {
          icon: LogoSVG,
          badge: LogoSVG,
          body,
          silent: true,
        });

        noti.onclick = () => {
          window.focus();
          if (!window.closed) navigate(invitesPath);
          noti.close();
          // Stop flashing when user clicks notification
          if ((window as any).electron?.window?.flashFrame) {
            (window as any).electron.window.flashFrame(false);
          }
        };
      }
    },
    [navigate]
  );

  const playSound = useCallback(() => {
    console.log('[InviteNotifications] playSound called, isElectron:', isElectron());
    if (isElectron() && (window as any).electron?.audio?.playNotificationSound) {
      console.log('[InviteNotifications] Using Electron audio API');
      (window as any).electron.audio.playNotificationSound('invite')
        .then((result: any) => console.log('[InviteNotifications] Sound result:', result))
        .catch((err: any) => console.error('[InviteNotifications] Sound error:', err));
    } else {
      console.log('[InviteNotifications] Using HTML5 Audio fallback');
      new Audio('./sound/invite.ogg').play().catch((err) => {
        console.error('[Audio] Failed to play invite sound:', err);
      });
    }
  }, []);

  useEffect(() => {
    if (invites.length > perviousInviteLen && mx.getSyncState() === 'SYNCING') {
      if (showNotifications && notificationPermission('granted')) {
        notify(invites.length - perviousInviteLen);
      }

      if (notificationSound) {
        playSound();
      }
    }
  }, [mx, invites, perviousInviteLen, showNotifications, notificationSound, notify, playSound]);

  return null;
}

function MessageNotifications() {
  const notifRef = useRef<Notification>();
  const unreadCacheRef = useRef<Map<string, UnreadInfo>>(new Map());
  const mx = useMatrixClient();
  const useAuthentication = useMediaAuthentication();
  const [showNotifications] = useSetting(settingsAtom, 'showNotifications');
  const [notificationSound] = useSetting(settingsAtom, 'isNotificationSounds');
  const mDirects = useAtomValue(mDirectAtom);
  const roomToParents = useAtomValue(roomToParentsAtom);

  const navigate = useNavigate();
  const notificationSelected = useInboxNotificationsSelected();
  const selectedRoomId = useSelectedRoom();

  // Set up notification tap listener for mobile
  useEffect(() => {
    setupNotificationTapListener((path) => {
      navigate(path);
    });
  }, [navigate]);

  const notify = useCallback(
    ({
      roomName,
      roomAvatar,
      username,
      messageBody,
      roomId,
      eventId,
      isDm,
    }: {
      roomName: string;
      roomAvatar?: string;
      username: string;
      messageBody?: string;
      roomId: string;
      eventId: string;
      isDm: boolean;
    }) => {
      const notificationTitle = username;
      const notificationBody = messageBody || 'New message';

      /** Replicates TitleBar click navigation logic */
      const navigateToRoom = () => {
        if (!mx) return;
        try {
          const roomIdOrAlias = getCanonicalAliasOrRoomId(mx, roomId);
          if (mDirects.has(roomId)) {
            navigate(getDirectRoomPath(roomIdOrAlias, eventId));
            return;
          }
          const orphanParents = getOrphanParents(roomToParents, roomId);
          if (orphanParents.length > 0) {
            const parentSpace = guessPerfectParent(mx, roomId, orphanParents) ?? orphanParents[0];
            const pSpaceIdOrAlias = getCanonicalAliasOrRoomId(mx, parentSpace);
            navigate(getSpaceRoomPath(pSpaceIdOrAlias, roomIdOrAlias, eventId));
            return;
          }
          navigate(getHomeRoomPath(roomIdOrAlias, eventId));
        } catch (err) {
          console.error('[Notifications] Navigate error:', err);
        }
      };

      // Flash taskbar icon for desktop notifications (only visible when window is not focused)
      console.log('[Notifications] Attempting flashFrame');
      if (isElectron() && (window as any).electron?.window?.flashFrame) {
        (window as any).electron.window.flashFrame(true)
          .then((result: { success: boolean }) => {
            console.log('[Notifications] flashFrame result:', result);
          })
          .catch((err: Error) => {
            console.error('[Notifications] flashFrame error:', err);
          });
      } else {
        console.warn('[Notifications] flashFrame not available, isElectron:', isElectron());
      }
      
      if ((isTauri() && !isElectron()) || isCapacitorNative()) {
        const roomPath = isDm
          ? getDirectRoomPath(roomId, eventId)
          : getHomeRoomPath(roomId, eventId);
        sendNotification({
          title: notificationTitle,
          body: notificationBody,
          path: roomPath,
          onClick: () => {
            if (!window.closed) navigate(roomPath);
          },
        });
      } else {
        // Use renderer window.Notification — works in both Electron and browser.
        // Main-process Notification click events are unreliable on Windows without
        // full COM/AUMID registration, so we keep everything in the renderer.
        const iconUrl = roomAvatar ?? '/res/android/android-chrome-192x192.png';

        const noti = new window.Notification(notificationTitle, {
          icon: iconUrl,
          body: notificationBody,
          silent: true,
        });

        noti.onclick = () => {
          // Tell main process to bring window to front
          (window as any).electron?.window?.focus();
          if (!window.closed) navigateToRoom();
          noti.close();
          notifRef.current = undefined;
          // Stop flashing when user clicks notification
          if ((window as any).electron?.window?.flashFrame) {
            (window as any).electron.window.flashFrame(false);
          }
        };

        notifRef.current?.close();
        notifRef.current = noti;
      }
    },
    [mx, navigate, mDirects, roomToParents]
  );

  const playSound = useCallback(() => {
    console.log('[MessageNotifications] playSound called, isElectron:', isElectron());
    if (isElectron() && (window as any).electron?.audio?.playNotificationSound) {
      console.log('[MessageNotifications] Using Electron audio API');
      (window as any).electron.audio.playNotificationSound('message')
        .then((result: any) => console.log('[MessageNotifications] Sound result:', result))
        .catch((err: any) => console.error('[MessageNotifications] Sound error:', err));
    } else {
      console.log('[MessageNotifications] Using HTML5 Audio fallback');
      new Audio('./sound/notification.ogg').play().catch((err) => {
        console.error('[Audio] Failed to play notification sound:', err);
      });
    }
  }, []);

  useEffect(() => {
    const handleTimelineEvent: RoomEventHandlerMap[RoomEvent.Timeline] = (
      mEvent,
      room,
      toStartOfTimeline,
      removed,
      data
    ) => {
      if (mx.getSyncState() !== 'SYNCING') return;
      if (document.hasFocus() && (selectedRoomId === room?.roomId || notificationSelected)) return;
      if (
        !room ||
        !data.liveEvent ||
        room.isSpaceRoom() ||
        !isNotificationEvent(mEvent) ||
        getNotificationType(mx, room.roomId) === NotificationType.Mute
      ) {
        return;
      }

      const sender = mEvent.getSender();
      const eventId = mEvent.getId();
      if (!sender || !eventId || mEvent.getSender() === mx.getUserId()) return;
      const unreadInfo = getUnreadInfo(room);
      const cachedUnreadInfo = unreadCacheRef.current.get(room.roomId);
      unreadCacheRef.current.set(room.roomId, unreadInfo);

      if (unreadInfo.total === 0) return;
      if (
        cachedUnreadInfo &&
        unreadEqual(unreadInfoToUnread(cachedUnreadInfo), unreadInfoToUnread(unreadInfo))
      ) {
        return;
      }

      // Match TitleBar / room bell settings: Default and Mentions rooms only notify on highlights.
      const notificationType = getNotificationType(mx, room.roomId);
      const isDm = mDirects.has(room.roomId);
      const mentionsOnly =
        notificationType === NotificationType.MentionsAndKeywords ||
        (notificationType === NotificationType.Default && !isDm);
      if (mentionsOnly && unreadInfo.highlight === 0) return;

      if (
        showNotifications &&
        ((isTauri() && !isElectron()) || isCapacitorNative() || notificationPermission('granted'))
      ) {
        const avatarMxc =
          room.getAvatarFallbackMember()?.getMxcAvatarUrl() ?? room.getMxcAvatarUrl();
        const content = mEvent.getContent();
        
        let messageBody: string | undefined;
        if (mEvent.getType() === 'm.reaction') {
          // For reactions, show "reacted with {emoji}"
          const reactionKey = content['m.relates_to']?.key;
          if (reactionKey) {
            messageBody = `reacted with ${reactionKey}`;
          } else {
            messageBody = 'reacted to a message';
          }
        } else {
          messageBody = typeof content.body === 'string' ? content.body : undefined;
        }
        
        notify({
          roomName: room.name ?? 'Unknown',
          roomAvatar: avatarMxc
            ? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined
            : undefined,
          username: getMemberDisplayName(room, sender) ?? getMxIdLocalPart(sender) ?? sender,
          messageBody,
          roomId: room.roomId,
          eventId,
          isDm,
        });
      }

      if (notificationSound) {
        playSound();
      }
    };
    mx.on(RoomEvent.Timeline, handleTimelineEvent);
    return () => {
      mx.removeListener(RoomEvent.Timeline, handleTimelineEvent);
    };
  }, [
    mx,
    notificationSound,
    notificationSelected,
    showNotifications,
    playSound,
    notify,
    selectedRoomId,
    useAuthentication,
    mDirects,
  ]);

  return null;
}

/**
 * Configures native Android push-ping background sync on login, keeps it
 * informed of foreground state so it doesn't double-fire notifications,
 * and clears it cleanly on unmount (logout).
 * Only active on Android Capacitor builds.
 */
function BackgroundSyncSetup() {
  const mx = useMatrixClient();
  // Try to make a simple fetch to verify component is rendering
  try {
    fetch('/_matrix/client/v3/sync', { method: 'HEAD' }).catch(() => {});
  } catch {}

  useEffect(() => {
    console.log('BackgroundSyncSetup: Starting background sync for', mx.getUserId());
    startBackgroundSync(mx);

    const onVisibility = () => setAppForegroundState(!document.hidden);
    document.addEventListener('visibilitychange', onVisibility);
    setAppForegroundState(!document.hidden);

    return () => {
      document.removeEventListener('visibilitychange', onVisibility);
      stopBackgroundSync();
    };
  }, [mx]);

  return null;
}

/**
 * Initializes the Paarrot API for Electron integration
 * Registers the navigate function and sets up IPC handlers
 */
function PaarrotAPIInitializer() {
  const navigate = useNavigate();
  const mx = useMatrixClient();

  useEffect(() => {
    // Register navigate function for Paarrot API
    setPaarrotNavigate(navigate);
    
    // Initialize Paarrot API handlers
    initPaarrotAPI(mx);
    
    console.log('Paarrot API: Initialized with navigate function');
  }, [navigate, mx]);

  return null;
}

/**
 * Stops taskbar icon flashing when window gains focus
 */
function TaskbarFlashStopper() {
  useEffect(() => {
    // Log what's available on mount
    console.log('[TaskbarFlashStopper] window.electron:', (window as any).electron);
    console.log('[TaskbarFlashStopper] Available APIs:', {
      hasWindow: !!(window as any).electron?.window,
      hasFlashFrame: !!(window as any).electron?.window?.flashFrame,
      hasAudio: !!(window as any).electron?.audio,
      hasPlaySound: !!(window as any).electron?.audio?.playNotificationSound,
    });

    const handleFocus = () => {
      if ((window as any).electron?.window?.flashFrame) {
        (window as any).electron.window.flashFrame(false);
      }
    };

    window.addEventListener('focus', handleFocus);
    return () => {
      window.removeEventListener('focus', handleFocus);
    };
  }, []);

  return null;
}

function AndroidShareIntentHandler() {
  const mx = useMatrixClient();
  const navigate = useNavigate();
  const [pendingShare, setPendingShare] = useState<AndroidSharePayload | null>(null);
  const mDirects = useAtomValue(mDirectAtom);
  const roomToParents = useAtomValue(roomToParentsAtom);

  const applyPendingShare = useCallback(
    async (share: AndroidSharePayload, roomId: string) => {
      const room = mx.getRoom(roomId);
      if (!room) return false;

      // Send text message if present
      const nextParts = [share.subject?.trim(), share.text?.trim()].filter(
        (value): value is string => !!value
      );
      if (nextParts.length > 0) {
        const textContent = {
          msgtype: 'm.text' as const,
          body: nextParts.join('\n'),
        };
        await mx.sendMessage(roomId, textContent);
      }

      // Upload and send files if present
      if (share.files.length > 0) {
        for (const sharedFile of share.files) {
          const originalFile = await materializeSharedFile(sharedFile, share.receivedAt);
          
          let fileToUpload = originalFile;
          let encInfo: any = undefined;

          if (room.hasEncryptionStateEvent()) {
            const encrypted = await encryptFile(originalFile);
            fileToUpload = encrypted.file;
            encInfo = encrypted.encInfo;
          }

          // Upload file
          const uploadResult = await mx.uploadContent(fileToUpload);
          const mxc = uploadResult?.content_uri;
          if (!mxc) continue;

          // Determine message type and send
          const fileType = originalFile.type;
          let msgtype = 'm.file' as const;
          if (fileType.startsWith('image/')) msgtype = 'm.image' as const;
          else if (fileType.startsWith('video/')) msgtype = 'm.video' as const;
          else if (fileType.startsWith('audio/')) msgtype = 'm.audio' as const;

          const fileContent: any = {
            msgtype,
            body: originalFile.name,
            filename: originalFile.name,
            info: {
              mimetype: originalFile.type,
              size: originalFile.size,
            },
          };

          if (encInfo) {
            fileContent.file = {
              ...encInfo,
              url: mxc,
            };
          } else {
            fileContent.url = mxc;
          }

          await mx.sendMessage(roomId, fileContent);
        }
      }

      await clearPendingAndroidShare();
      return true;
    },
    [mx]
  );

  const handlePickRoom = useCallback(
    (roomId: string) => {
      if (!pendingShare) return;

      applyPendingShare(pendingShare, roomId)
        .then((applied) => {
          if (applied) {
            setPendingShare(null);
            
            // Navigate to the selected room
            const isDirect = mDirects.has(roomId);
            if (isDirect) {
              navigate(getDirectRoomPath(roomId));
            } else {
              const parents = roomToParents.get(roomId);
              const parent = parents && parents.length > 0 ? parents[0] : undefined;
              if (parent) {
                navigate(getSpaceRoomPath(parent, roomId));
              } else {
                navigate(getHomeRoomPath(roomId));
              }
            }
          }
        })
        .catch((err) => {
          console.error('[AndroidShare] Failed to apply share after pick:', err);
          setPendingShare(null);
        });
    },
    [applyPendingShare, pendingShare, navigate, mDirects, roomToParents]
  );

  const handleDismiss = useCallback(() => {
    clearPendingAndroidShare().catch(() => {});
    setPendingShare(null);
  }, []);

  useEffect(() => {
    if (!isAndroidShareSupported()) return;

    let mounted = true;
    let listenerHandle: { remove: () => Promise<void> } | undefined;

    getPendingAndroidShare()
      .then((share) => {
        if (mounted && share) {
          setPendingShare(share);
        }
      })
      .catch((err) => {
        console.error('[AndroidShare] Failed to get pending share:', err);
      });

    listenForAndroidShares((share) => {
      if (mounted) {
        setPendingShare(share);
      }
    })
      .then((handle) => {
        listenerHandle = handle;
      })
      .catch((err) => {
        console.error('[AndroidShare] Failed to listen for shares:', err);
      });

    return () => {
      mounted = false;
      void listenerHandle?.remove();
    };
  }, []);

  if (!pendingShare) return null;

  return (
    <ShareRoomPicker
      share={pendingShare}
      onPick={handlePickRoom}
      onDismiss={handleDismiss}
    />
  );
}

type ClientNonUIFeaturesProps = {
  children: ReactNode;
};

export function ClientNonUIFeatures({ children }: ClientNonUIFeaturesProps) {
  return (
    <>
      <EmojiStyleFeature />
      <PageZoomFeature />
      <FaviconUpdater />
      <InviteNotifications />
      <MessageNotifications />
      <BackgroundSyncSetup />
      <PaarrotAPIInitializer />
      <TaskbarFlashStopper />
      <AndroidShareIntentHandler />
      {children}
    </>
  );
}
