import { Room } from 'matrix-js-sdk';
import React, {
  useContext,
  useCallback,
  useEffect,
  useRef,
  MouseEventHandler,
  useState,
  ReactNode,
} from 'react';
import { Box, Button, config, Spinner, Text } from 'folds';
import { useCallState } from '../../pages/client/call/CallProvider';
import { useCallMembers } from '../../hooks/useCallMemberships';

import { CallRefContext } from '../../pages/client/call/PersistentCallContainer';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { useDebounce } from '../../hooks/useDebounce';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { CallViewUser } from './CallViewUser';
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
import { getMemberDisplayName } from '../../utils/room';
import { getMxIdLocalPart } from '../../utils/matrix';
import * as css from './CallView.css';

type OriginalStyles = {
  position?: string;
  top?: string;
  left?: string;
  width?: string;
  height?: string;
  zIndex?: string;
  display?: string;
  visibility?: string;
  pointerEvents?: string;
  border?: string;
};

export function CallViewUserGrid({ children }: { children: ReactNode }) {
  return (
    <Box
      className={css.CallViewUserGrid}
      style={{
        maxWidth: React.Children.count(children) === 4 ? '336px' : '503px',
      }}
    >
      {children}
    </Box>
  );
}

export function CallView({ room }: { room: Room }) {
  const callIframeRef = useContext(CallRefContext);
  const iframeHostRef = useRef<HTMLDivElement>(null);

  const originalIframeStylesRef = useRef<OriginalStyles | null>(null);
  const mx = useMatrixClient();

  const [visibleCallNames, setVisibleCallNames] = useState('');

  const {
    isActiveCallReady,
    activeCallRoomId,
    isChatOpen,
    setActiveCallRoomId,
    hangUp,
    setViewedCallRoomId,
  } = useCallState();

  const isActiveCallRoom = activeCallRoomId === room.roomId;
  const callIsCurrentAndReady = isActiveCallRoom && isActiveCallReady;
  const callMembers = useCallMembers(mx, room.roomId);

  const getName = (userId: string) =>
    getMemberDisplayName(room, userId) ?? getMxIdLocalPart(userId);

  const memberDisplayNames = callMembers.map((callMembership) =>
    getName(callMembership.sender ?? '')
  );

  const { navigateRoom } = useRoomNavigate();
  const screenSize = useScreenSizeContext();
  const isMobile = screenSize === ScreenSize.Mobile;

  const activeIframeDisplayRef = callIframeRef;

  const applyFixedPositioningToIframe = useCallback(() => {
    const iframeElement = activeIframeDisplayRef?.current;
    const hostElement = iframeHostRef?.current;

    if (iframeElement && hostElement) {
      if (!originalIframeStylesRef.current) {
        const computed = window.getComputedStyle(iframeElement);
        originalIframeStylesRef.current = {
          position: iframeElement.style.position || computed.position,
          top: iframeElement.style.top || computed.top,
          left: iframeElement.style.left || computed.left,
          width: iframeElement.style.width || computed.width,
          height: iframeElement.style.height || computed.height,
          zIndex: iframeElement.style.zIndex || computed.zIndex,
          display: iframeElement.style.display || computed.display,
          visibility: iframeElement.style.visibility || computed.visibility,
          pointerEvents: iframeElement.style.pointerEvents || computed.pointerEvents,
          border: iframeElement.style.border || computed.border,
        };
      }

      const hostRect = hostElement.getBoundingClientRect();

      iframeElement.style.position = 'fixed';
      iframeElement.style.top = `${hostRect.top}px`;
      iframeElement.style.left = `${hostRect.left}px`;
      iframeElement.style.width = `${hostRect.width}px`;
      iframeElement.style.height = `${hostRect.height}px`;
      iframeElement.style.border = 'none';
      iframeElement.style.zIndex = '1000';
      iframeElement.style.display = room.isCallRoom() ? 'block' : 'none';
      iframeElement.style.visibility = 'visible';
      iframeElement.style.pointerEvents = 'auto';
    }
  }, [activeIframeDisplayRef, room]);

  const debouncedApplyFixedPositioning = useDebounce(applyFixedPositioningToIframe, {
    wait: 50,
    immediate: false,
  });
  useEffect(() => {
    const iframeElement = activeIframeDisplayRef?.current;
    const hostElement = iframeHostRef?.current;

    if (room.isCallRoom() || (callIsCurrentAndReady && iframeElement && hostElement)) {
      applyFixedPositioningToIframe();

      const resizeObserver = new ResizeObserver(debouncedApplyFixedPositioning);
      if (hostElement) resizeObserver.observe(hostElement);
      window.addEventListener('scroll', debouncedApplyFixedPositioning, true);

      return () => {
        resizeObserver.disconnect();
        window.removeEventListener('scroll', debouncedApplyFixedPositioning, true);

        if (iframeElement && originalIframeStylesRef.current) {
          const originalStyles = originalIframeStylesRef.current;
          (Object.keys(originalStyles) as Array<keyof OriginalStyles>).forEach((key) => {
            if (key in iframeElement.style) {
              iframeElement.style[key as any] = originalStyles[key] || '';
            }
          });
        }
        originalIframeStylesRef.current = null;
      };
    }

    return undefined;
  }, [
    activeIframeDisplayRef,
    applyFixedPositioningToIframe,
    debouncedApplyFixedPositioning,
    callIsCurrentAndReady,
    room,
  ]);

  const handleJoinVCClick: MouseEventHandler<HTMLElement> = (evt) => {
    if (isMobile) {
      evt.stopPropagation();
      setViewedCallRoomId(room.roomId);
      navigateRoom(room.roomId);
    }
    if (!callIsCurrentAndReady) {
      hangUp();
      setActiveCallRoomId(room.roomId);
    }
  };

  const isCallViewVisible = room.isCallRoom() && (screenSize === ScreenSize.Desktop || !isChatOpen);

  useEffect(() => {
    if (memberDisplayNames.length <= 2) {
      setVisibleCallNames(memberDisplayNames.join(' and '));
    } else {
      const visible = memberDisplayNames.slice(0, 2);
      const remaining = memberDisplayNames.length - 2;

      setVisibleCallNames(
        `${visible.join(', ')}, and ${remaining} other${remaining > 1 ? 's' : ''}`
      );
    }
  }, [memberDisplayNames]);

  return (
    <Box grow="Yes" direction="Column" style={{ display: isCallViewVisible ? 'flex' : 'none' }}>
      <div
        ref={iframeHostRef}
        style={{
          width: '100%',
          height: '100%',
          position: 'relative',
          pointerEvents: 'none',
          display: callIsCurrentAndReady ? 'flex' : 'none',
        }}
      />
      <Box
        grow="Yes"
        justifyContent="Center"
        alignItems="Center"
        direction="Column"
        gap="300"
        style={{
          display: callIsCurrentAndReady ? 'none' : 'flex',
        }}
      >
        <CallViewUserGrid>
          {callMembers.slice(0, 6).map((callMember) => (
            <CallViewUser
              key={callMember.membershipID}
              room={room}
              callMembership={callMember}
            />
          ))}
        </CallViewUserGrid>

        <Box
          direction="Column"
          alignItems="Center"
          style={{
            paddingBlock: config.space.S200,
          }}
        >
          <Text
            size="H1"
            style={{
              paddingBottom: config.space.S300,
            }}
          >
            {room.name}
          </Text>
          <Text size="T200">
            {visibleCallNames !== '' ? visibleCallNames : 'No one'}{' '}
            {memberDisplayNames.length > 1 ? 'are' : 'is'} currently in voice
          </Text>
        </Box>
        <Button variant="Secondary" disabled={isActiveCallRoom} onClick={handleJoinVCClick}>
          {isActiveCallRoom ? (
            <Box justifyContent="Center" alignItems="Center" gap="200">
              <Spinner />
              <Text size="B500">{activeCallRoomId === room.roomId ? `Joining` : 'Join Voice'}</Text>
            </Box>
          ) : (
            <Text size="B500">Join Voice</Text>
          )}
        </Button>
      </Box>
    </Box>
  );
}
