// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

import React from 'react';
import FocusTrap from 'focus-trap-react';
import classNames from 'classnames';
import type {
  SetLocalAudioType,
  SetLocalPreviewType,
  SetLocalVideoType,
} from '../state/ducks/calling';
import { CallingButton, CallingButtonType } from './CallingButton';
import { TooltipPlacement } from './Tooltip';
import { CallBackgroundBlur } from './CallBackgroundBlur';
import { CallParticipantCount } from './CallParticipantCount';
import { CallingHeader } from './CallingHeader';
import { CallingPreCallInfo, RingMode } from './CallingPreCallInfo';
import {
  CallingLobbyJoinButton,
  CallingLobbyJoinButtonVariant,
} from './CallingLobbyJoinButton';
import type { LocalizerType } from '../types/Util';
import { useIsOnline } from '../hooks/useIsOnline';
import * as KeyboardLayout from '../services/keyboardLayout';
import type { ConversationType } from '../state/ducks/conversations';
import { useCallingToasts } from './CallingToast';
import { CallingButtonToastsContainer } from './CallingToastManager';

export type PropsType = {
  availableCameras: Array<MediaDeviceInfo>;
  conversation: Pick<
    ConversationType,
    | 'acceptedMessageRequest'
    | 'avatarPath'
    | 'color'
    | 'isMe'
    | 'memberships'
    | 'name'
    | 'phoneNumber'
    | 'profileName'
    | 'sharedGroupNames'
    | 'title'
    | 'type'
    | 'unblurredAvatarPath'
  >;
  groupMembers?: Array<Pick<ConversationType, 'id' | 'firstName' | 'title'>>;
  hasLocalAudio: boolean;
  hasLocalVideo: boolean;
  i18n: LocalizerType;
  isConversationTooBigToRing: boolean;
  isGroupCall: boolean;
  isGroupCallOutboundRingEnabled: boolean;
  isCallFull?: boolean;
  me: Readonly<
    Pick<ConversationType, 'avatarPath' | 'color' | 'id' | 'serviceId'>
  >;
  onCallCanceled: () => void;
  onJoinCall: () => void;
  outgoingRing: boolean;
  peekedParticipants: Array<ConversationType>;
  setLocalAudio: (_: SetLocalAudioType) => void;
  setLocalVideo: (_: SetLocalVideoType) => void;
  setLocalPreview: (_: SetLocalPreviewType) => void;
  setOutgoingRing: (_: boolean) => void;
  showParticipantsList: boolean;
  toggleParticipants: () => void;
  toggleSettings: () => void;
};

export function CallingLobby({
  availableCameras,
  conversation,
  groupMembers,
  hasLocalAudio,
  hasLocalVideo,
  i18n,
  isGroupCall = false,
  isGroupCallOutboundRingEnabled,
  isCallFull = false,
  isConversationTooBigToRing,
  me,
  onCallCanceled,
  onJoinCall,
  peekedParticipants,
  setLocalAudio,
  setLocalPreview,
  setLocalVideo,
  setOutgoingRing,
  toggleParticipants,
  toggleSettings,
  outgoingRing,
}: PropsType): JSX.Element {
  const localVideoRef = React.useRef<null | HTMLVideoElement>(null);

  const shouldShowLocalVideo = hasLocalVideo && availableCameras.length > 0;

  const toggleAudio = React.useCallback((): void => {
    setLocalAudio({ enabled: !hasLocalAudio });
  }, [hasLocalAudio, setLocalAudio]);

  const toggleVideo = React.useCallback((): void => {
    setLocalVideo({ enabled: !hasLocalVideo });
  }, [hasLocalVideo, setLocalVideo]);

  const toggleOutgoingRing = React.useCallback((): void => {
    setOutgoingRing(!outgoingRing);
  }, [outgoingRing, setOutgoingRing]);

  React.useEffect(() => {
    setLocalPreview({ element: localVideoRef });

    return () => {
      setLocalPreview({ element: undefined });
    };
  }, [setLocalPreview]);

  React.useEffect(() => {
    function handleKeyDown(event: KeyboardEvent): void {
      let eventHandled = false;

      const key = KeyboardLayout.lookup(event);
      if (event.shiftKey && (key === 'V' || key === 'v')) {
        toggleVideo();
        eventHandled = true;
      } else if (event.shiftKey && (key === 'M' || key === 'm')) {
        toggleAudio();
        eventHandled = true;
      }

      if (eventHandled) {
        event.preventDefault();
        event.stopPropagation();
      }
    }

    document.addEventListener('keydown', handleKeyDown);

    return () => {
      document.removeEventListener('keydown', handleKeyDown);
    };
  }, [toggleVideo, toggleAudio]);

  const isOnline = useIsOnline();

  const [isCallConnecting, setIsCallConnecting] = React.useState(false);

  // eslint-disable-next-line no-nested-ternary
  const videoButtonType = hasLocalVideo
    ? CallingButtonType.VIDEO_ON
    : availableCameras.length === 0
    ? CallingButtonType.VIDEO_DISABLED
    : CallingButtonType.VIDEO_OFF;

  const audioButtonType = hasLocalAudio
    ? CallingButtonType.AUDIO_ON
    : CallingButtonType.AUDIO_OFF;

  const isRingButtonVisible: boolean =
    isGroupCall &&
    isGroupCallOutboundRingEnabled &&
    peekedParticipants.length === 0 &&
    (groupMembers || []).length > 1;

  let preCallInfoRingMode: RingMode;
  if (isGroupCall) {
    preCallInfoRingMode =
      outgoingRing && !isConversationTooBigToRing
        ? RingMode.WillRing
        : RingMode.WillNotRing;
  } else {
    preCallInfoRingMode = RingMode.WillRing;
  }

  let ringButtonType:
    | CallingButtonType.RING_DISABLED
    | CallingButtonType.RING_ON
    | CallingButtonType.RING_OFF;
  if (isRingButtonVisible) {
    if (isConversationTooBigToRing) {
      ringButtonType = CallingButtonType.RING_DISABLED;
    } else if (outgoingRing) {
      ringButtonType = CallingButtonType.RING_ON;
    } else {
      ringButtonType = CallingButtonType.RING_OFF;
    }
  } else {
    ringButtonType = CallingButtonType.RING_DISABLED;
  }

  const canJoin = !isCallFull && !isCallConnecting && isOnline;

  let callingLobbyJoinButtonVariant: CallingLobbyJoinButtonVariant;
  if (isCallFull) {
    callingLobbyJoinButtonVariant = CallingLobbyJoinButtonVariant.CallIsFull;
  } else if (isCallConnecting) {
    callingLobbyJoinButtonVariant = CallingLobbyJoinButtonVariant.Loading;
  } else if (peekedParticipants.length) {
    callingLobbyJoinButtonVariant = CallingLobbyJoinButtonVariant.Join;
  } else {
    callingLobbyJoinButtonVariant = CallingLobbyJoinButtonVariant.Start;
  }

  const callStatus = React.useMemo(() => {
    if (isGroupCall) {
      return (
        <CallParticipantCount
          i18n={i18n}
          groupMemberCount={groupMembers?.length ?? 0}
          participantCount={peekedParticipants.length}
          toggleParticipants={toggleParticipants}
        />
      );
    }
    if (hasLocalVideo) {
      return i18n('icu:ContactListItem__menu__video-call');
    }
    if (hasLocalAudio) {
      return i18n('icu:CallControls__InfoDisplay--audio-call');
    }
    return null;
  }, [
    isGroupCall,
    peekedParticipants.length,
    i18n,
    hasLocalVideo,
    hasLocalAudio,
    groupMembers?.length,
    toggleParticipants,
  ]);

  useWasInitiallyMutedToast(hasLocalAudio, i18n);

  return (
    <FocusTrap>
      <div className="module-calling__container">
        {shouldShowLocalVideo ? (
          <video
            className="module-CallingLobby__local-preview module-CallingLobby__local-preview--camera-is-on"
            ref={localVideoRef}
            autoPlay
          />
        ) : (
          <CallBackgroundBlur
            className="module-CallingLobby__local-preview module-CallingLobby__local-preview--camera-is-off"
            avatarPath={me.avatarPath}
            color={me.color}
          />
        )}

        <CallingHeader
          i18n={i18n}
          isGroupCall={isGroupCall}
          participantCount={peekedParticipants.length}
          toggleSettings={toggleSettings}
          onCancel={onCallCanceled}
        />

        <div className="module-calling__spacer module-CallingPreCallInfo-spacer" />
        <CallingPreCallInfo
          conversation={conversation}
          groupMembers={groupMembers}
          i18n={i18n}
          isCallFull={isCallFull}
          me={me}
          peekedParticipants={peekedParticipants}
          ringMode={preCallInfoRingMode}
        />

        <div
          className={classNames(
            'module-calling__camera-is-off module-CallingLobby__camera-is-off',
            `module-CallingLobby__camera-is-off--${
              shouldShowLocalVideo ? 'invisible' : 'visible'
            }`
          )}
        >
          {i18n('icu:calling__your-video-is-off')}
        </div>

        <div className="CallingLobby__Footer">
          <div className="module-calling__spacer CallControls__OuterSpacer" />
          <div className="CallControls">
            <div className="CallControls__InfoDisplay">
              <div className="CallControls__CallTitle">
                {conversation.title}
              </div>
              <div className="CallControls__Status">{callStatus}</div>
            </div>
            <CallingButtonToastsContainer
              hasLocalAudio={hasLocalAudio}
              outgoingRing={outgoingRing}
              i18n={i18n}
            />
            <div className="CallControls__ButtonContainer">
              <CallingButton
                buttonType={videoButtonType}
                i18n={i18n}
                onClick={toggleVideo}
                tooltipDirection={TooltipPlacement.Top}
              />
              <CallingButton
                buttonType={audioButtonType}
                i18n={i18n}
                onClick={toggleAudio}
                tooltipDirection={TooltipPlacement.Top}
              />
              <CallingButton
                buttonType={ringButtonType}
                i18n={i18n}
                isVisible={isRingButtonVisible}
                onClick={toggleOutgoingRing}
                tooltipDirection={TooltipPlacement.Top}
              />
            </div>
            <div className="CallControls__JoinLeaveButtonContainer">
              <CallingLobbyJoinButton
                disabled={!canJoin}
                i18n={i18n}
                onClick={() => {
                  setIsCallConnecting(true);
                  onJoinCall();
                }}
                variant={callingLobbyJoinButtonVariant}
              />
            </div>
          </div>
          <div className="module-calling__spacer CallControls__OuterSpacer" />
        </div>
      </div>
    </FocusTrap>
  );
}

function useWasInitiallyMutedToast(
  hasLocalAudio: boolean,
  i18n: LocalizerType
) {
  const [wasInitiallyMuted] = React.useState(!hasLocalAudio);
  const { showToast, hideToast } = useCallingToasts();
  const INITIALLY_MUTED_KEY = 'initially-muted-group-size';
  React.useEffect(() => {
    if (wasInitiallyMuted) {
      showToast({
        key: INITIALLY_MUTED_KEY,
        content: i18n(
          'icu:calling__lobby-automatically-muted-because-there-are-a-lot-of-people'
        ),
        autoClose: true,
        dismissable: true,
        onlyShowOnce: true,
      });
    }
  }, [wasInitiallyMuted, i18n, showToast]);

  // Hide this toast if the user unmutes
  React.useEffect(() => {
    if (wasInitiallyMuted && hasLocalAudio) {
      hideToast(INITIALLY_MUTED_KEY);
    }
  }, [hideToast, wasInitiallyMuted, hasLocalAudio]);
}