import type { FC } from '../../lib/teact/teact';
import React, {
  memo, useEffect, useMemo, useRef,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';

import type {
  ApiMessage, ApiPeer, ApiPhoto, ApiUser,
} from '../../api/types';
import { MediaViewerOrigin, type ThreadId } from '../../types';

import { ANIMATION_END_DELAY } from '../../config';
import { getChatMediaMessageIds, isChatAdmin, isUserId } from '../../global/helpers';
import {
  selectChat,
  selectChatMessage,
  selectChatMessages,
  selectChatScheduledMessages,
  selectCurrentChatMediaSearch,
  selectCurrentSharedMediaSearch,
  selectIsChatWithSelf,
  selectListedIds,
  selectOutlyingListByMessageId,
  selectPerformanceSettingsValue,
  selectScheduledMessage,
  selectTabState,
  selectUser,
  selectUserFullInfo,
} from '../../global/selectors';
import { stopCurrentAudio } from '../../util/audioPlayer';
import captureEscKeyListener from '../../util/captureEscKeyListener';
import { disableDirectTextInput, enableDirectTextInput } from '../../util/directInputManager';
import { MEDIA_VIEWER_MEDIA_QUERY } from '../common/helpers/mediaDimensions';
import { renderMessageText } from '../common/helpers/renderMessageText';
import { animateClosing, animateOpening } from './helpers/ghostAnimation';

import useAppLayout from '../../hooks/useAppLayout';
import useElectronDrag from '../../hooks/useElectronDrag';
import useFlag from '../../hooks/useFlag';
import useForceUpdate from '../../hooks/useForceUpdate';
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
import useLastCallback from '../../hooks/useLastCallback';
import useOldLang from '../../hooks/useOldLang';
import { exitPictureInPictureIfNeeded, usePictureInPictureSignal } from '../../hooks/usePictureInPicture';
import usePrevious from '../../hooks/usePrevious';
import { dispatchPriorityPlaybackEvent } from '../../hooks/usePriorityPlaybackCheck';
import { useStateRef } from '../../hooks/useStateRef';
import { useMediaProps } from './hooks/useMediaProps';

import ReportModal from '../common/ReportModal';
import Button from '../ui/Button';
import ShowTransition from '../ui/ShowTransition';
import Transition from '../ui/Transition';
import MediaViewerActions from './MediaViewerActions';
import MediaViewerSlides from './MediaViewerSlides';
import SenderInfo from './SenderInfo';

import './MediaViewer.scss';

type StateProps = {
  chatId?: string;
  threadId?: ThreadId;
  mediaId?: number;
  senderId?: string;
  isChatWithSelf?: boolean;
  canUpdateMedia?: boolean;
  origin?: MediaViewerOrigin;
  avatarOwner?: ApiPeer;
  avatarOwnerFallbackPhoto?: ApiPhoto;
  message?: ApiMessage;
  chatMessages?: Record<number, ApiMessage>;
  collectionIds?: number[];
  isHidden?: boolean;
  withAnimation?: boolean;
  shouldSkipHistoryAnimations?: boolean;
  withDynamicLoading?: boolean;
  isLoadingMoreMedia?: boolean;
  isSynced?: boolean;
};

const ANIMATION_DURATION = 250;

const MediaViewer: FC<StateProps> = ({
  chatId,
  threadId,
  mediaId,
  senderId,
  isChatWithSelf,
  canUpdateMedia,
  origin,
  avatarOwner,
  avatarOwnerFallbackPhoto,
  message,
  chatMessages,
  collectionIds,
  withAnimation,
  isHidden,
  shouldSkipHistoryAnimations,
  withDynamicLoading,
  isLoadingMoreMedia,
  isSynced,
}) => {
  const {
    openMediaViewer,
    closeMediaViewer,
    openForwardMenu,
    focusMessage,
    toggleChatInfo,
    searchChatMediaMessages,
  } = getActions();

  const isOpen = Boolean(avatarOwner || mediaId);
  const { isMobile } = useAppLayout();

  /* Animation */
  const animationKey = useRef<number>();
  const prevSenderId = usePrevious<string | undefined>(senderId);
  const headerAnimation = withAnimation ? 'slideFade' : 'none';
  const isGhostAnimation = Boolean(withAnimation && !shouldSkipHistoryAnimations);

  /* Controls */
  const [isReportModalOpen, openReportModal, closeReportModal] = useFlag();

  const {
    webPagePhoto,
    webPageVideo,
    isVideo,
    actionPhoto,
    isPhoto,
    bestImageData,
    bestData,
    dimensions,
    isGif,
    isFromSharedMedia,
    avatarPhoto,
    fileName,
  } = useMediaProps({
    message, avatarOwner, mediaId, origin, delay: isGhostAnimation && ANIMATION_DURATION,
  });

  const canReport = !!avatarPhoto && !isChatWithSelf;
  const isVisible = !isHidden && isOpen;

  /* Navigation */
  const singleMediaId = webPagePhoto || webPageVideo || actionPhoto || isGif ? mediaId : undefined;

  const mediaIds = useMemo(() => {
    if (singleMediaId) return [singleMediaId];
    if (avatarOwner) return avatarOwner.photos?.map((p, i) => i) || [];
    if (withDynamicLoading) return collectionIds || [];
    return getChatMediaMessageIds(chatMessages || {}, collectionIds || [], isFromSharedMedia);
  }, [singleMediaId, avatarOwner, chatMessages, collectionIds, isFromSharedMedia, withDynamicLoading]);

  const selectedMediaIndex = mediaId ? mediaIds.indexOf(mediaId) : -1;

  if (isOpen && (!prevSenderId || prevSenderId !== senderId || !animationKey.current)) {
    animationKey.current = selectedMediaIndex;
  }

  const [getIsPictureInPicture] = usePictureInPictureSignal();

  useEffect(() => {
    if (!isOpen || getIsPictureInPicture()) {
      return undefined;
    }

    disableDirectTextInput();
    const stopPriorityPlayback = dispatchPriorityPlaybackEvent();

    return () => {
      stopPriorityPlayback();
      enableDirectTextInput();
    };
  }, [isOpen, getIsPictureInPicture]);

  useEffect(() => {
    if (isVisible) {
      exitPictureInPictureIfNeeded();
    }
  }, [isVisible]);

  useEffect(() => {
    if (isMobile) {
      document.body.classList.toggle('is-media-viewer-open', isOpen);
    }
  }, [isMobile, isOpen]);

  // eslint-disable-next-line no-null/no-null
  const headerRef = useRef<HTMLDivElement>(null);
  useElectronDrag(headerRef);

  const forceUpdate = useForceUpdate();
  useEffect(() => {
    const mql = window.matchMedia(MEDIA_VIEWER_MEDIA_QUERY);
    mql.addEventListener('change', forceUpdate);

    return () => {
      mql.removeEventListener('change', forceUpdate);
    };
  }, [forceUpdate]);

  const prevMessage = usePrevious<ApiMessage | undefined>(message);
  const prevIsHidden = usePrevious<boolean | undefined>(isHidden);
  const prevOrigin = usePrevious(origin);
  const prevMediaId = usePrevious(mediaId);
  const prevAvatarOwner = usePrevious<ApiPeer | undefined>(avatarOwner);
  const prevBestImageData = usePrevious(bestImageData);
  const textParts = message ? renderMessageText({ message, forcePlayback: true, isForMediaViewer: true }) : undefined;
  const hasFooter = Boolean(textParts);
  const shouldAnimateOpening = prevIsHidden && prevMediaId !== mediaId;

  useEffect(() => {
    if (isGhostAnimation && isOpen && (!prevMessage || shouldAnimateOpening) && !prevAvatarOwner) {
      dispatchHeavyAnimationEvent(ANIMATION_DURATION + ANIMATION_END_DELAY);
      animateOpening(hasFooter, origin!, bestImageData!, dimensions!, isVideo, message);
    }

    if (isGhostAnimation && !isOpen && (prevMessage || prevAvatarOwner)) {
      dispatchHeavyAnimationEvent(ANIMATION_DURATION + ANIMATION_END_DELAY);
      animateClosing(prevOrigin!, prevBestImageData!, prevMessage || undefined);
    }
  }, [
    isGhostAnimation, isOpen, shouldAnimateOpening, origin, prevOrigin, message, prevMessage, prevAvatarOwner,
    bestImageData, prevBestImageData, dimensions, isVideo, hasFooter,
  ]);

  const handleClose = useLastCallback(() => closeMediaViewer());

  const mediaIdRef = useStateRef(mediaId);
  const handleFooterClick = useLastCallback(() => {
    handleClose();

    const currentMediaId = mediaIdRef.current;

    if (!chatId || !currentMediaId) return;

    if (isMobile) {
      setTimeout(() => {
        toggleChatInfo({ force: false }, { forceSyncOnIOs: true });
        focusMessage({ chatId, threadId, messageId: currentMediaId });
      }, ANIMATION_DURATION);
    } else {
      focusMessage({ chatId, threadId, messageId: currentMediaId });
    }
  });

  const handleForward = useLastCallback(() => {
    openForwardMenu({
      fromChatId: chatId!,
      messageIds: [mediaId!],
    });
  });

  const selectMedia = useLastCallback((id?: number) => {
    openMediaViewer({
      chatId,
      threadId,
      mediaId: id,
      avatarOwnerId: avatarOwner?.id,
      origin: origin!,
      withDynamicLoading,
    }, {
      forceOnHeavyAnimation: true,
    });
  });

  useEffect(() => (isOpen ? captureEscKeyListener(() => {
    handleClose();
  }) : undefined), [handleClose, isOpen]);

  useEffect(() => {
    if (isVideo && !isGif) {
      stopCurrentAudio();
    }
  }, [isGif, isVideo]);

  const mediaIdsRef = useStateRef(mediaIds);

  const loadMoreMediaIfNeeded = useLastCallback((activeMediaId?: number) => {
    if (!activeMediaId || !withDynamicLoading || isLoadingMoreMedia) return;
    searchChatMediaMessages({ chatId, threadId, currentMediaMessageId: activeMediaId });
  });

  const getMediaId = useLastCallback((fromId?: number, direction?: number): number | undefined => {
    if (fromId === undefined) return undefined;
    const mIds = mediaIdsRef.current;
    const index = mIds.indexOf(fromId);
    if ((direction === -1 && index > 0) || (direction === 1 && index < mIds.length - 1)) {
      return mIds[index + direction];
    }
    // Fallback
    if (isVisible) loadMoreMediaIfNeeded(fromId);
    return undefined;
  });

  const handleBeforeDelete = useLastCallback(() => {
    if (mediaIds.length <= 1) {
      handleClose();
      return;
    }
    let index = mediaId ? mediaIds.indexOf(mediaId) : -1;
    // Before deleting, select previous media or the first one
    index = index > 0 ? index - 1 : 0;
    selectMedia(mediaIds[index]);
  });

  const lang = useOldLang();

  function renderSenderInfo() {
    return avatarOwner ? (
      <SenderInfo
        key={mediaId}
        chatId={avatarOwner.id}
        isAvatar
        isFallbackAvatar={isUserId(avatarOwner.id)
          && (avatarOwner as ApiUser).photos?.[mediaId!]?.id === avatarOwnerFallbackPhoto?.id}
      />
    ) : (
      <SenderInfo
        key={mediaId}
        chatId={chatId}
        messageId={mediaId}
      />
    );
  }

  return (
    <ShowTransition
      id="MediaViewer"
      isOpen={isOpen}
      isHidden={isHidden}
      shouldAnimateFirstRender
      noCloseTransition={shouldSkipHistoryAnimations}
    >
      <div className="media-viewer-head" dir={lang.isRtl ? 'rtl' : undefined} ref={headerRef}>
        {isMobile && (
          <Button
            className="media-viewer-close"
            round
            size="smaller"
            color="translucent-white"
            ariaLabel={lang('Close')}
            onClick={handleClose}
          >
            <i className="icon icon-close" />
          </Button>
        )}
        <Transition activeKey={animationKey.current!} name={headerAnimation}>
          {renderSenderInfo()}
        </Transition>
        <MediaViewerActions
          mediaData={bestData}
          isVideo={isVideo}
          message={message}
          canUpdateMedia={canUpdateMedia}
          avatarPhoto={avatarPhoto}
          avatarOwner={avatarOwner}
          fileName={fileName}
          canReport={canReport}
          selectMedia={selectMedia}
          onBeforeDelete={handleBeforeDelete}
          onReport={openReportModal}
          onCloseMediaViewer={handleClose}
          onForward={handleForward}
        />
        <ReportModal
          isOpen={isReportModalOpen}
          onClose={closeReportModal}
          subject="media"
          photo={avatarPhoto}
          peerId={avatarOwner?.id}
        />
      </div>
      <MediaViewerSlides
        mediaId={mediaId}
        loadMoreMediaIfNeeded={loadMoreMediaIfNeeded}
        isLoadingMoreMedia={isLoadingMoreMedia}
        isSynced={isSynced}
        getMediaId={getMediaId}
        chatId={chatId}
        isPhoto={isPhoto}
        isGif={isGif}
        threadId={threadId}
        avatarOwnerId={avatarOwner?.id}
        origin={origin}
        isOpen={isOpen}
        hasFooter={hasFooter}
        isVideo={isVideo}
        withAnimation={withAnimation}
        onClose={handleClose}
        selectMedia={selectMedia}
        isHidden={isHidden}
        onFooterClick={handleFooterClick}
      />
    </ShowTransition>
  );
};

export default memo(withGlobal(
  (global): StateProps => {
    const { mediaViewer, shouldSkipHistoryAnimations } = selectTabState(global);
    const {
      chatId,
      threadId,
      mediaId,
      avatarOwnerId,
      origin,
      isHidden,
      withDynamicLoading,
    } = mediaViewer;
    const withAnimation = selectPerformanceSettingsValue(global, 'mediaViewerAnimations');

    const { currentUserId, isSynced } = global;
    let isChatWithSelf = !!chatId && selectIsChatWithSelf(global, chatId);

    if (origin === MediaViewerOrigin.SearchResult) {
      if (!(chatId && mediaId)) {
        return { withAnimation, shouldSkipHistoryAnimations };
      }

      const message = selectChatMessage(global, chatId, mediaId);
      if (!message) {
        return { withAnimation, shouldSkipHistoryAnimations };
      }

      return {
        chatId,
        mediaId,
        senderId: message.senderId,
        isChatWithSelf,
        origin,
        message,
        withAnimation,
        isHidden,
        shouldSkipHistoryAnimations,
      };
    }

    if (avatarOwnerId) {
      const user = selectUser(global, avatarOwnerId);
      const chat = selectChat(global, avatarOwnerId);
      let canUpdateMedia = false;
      if (user) {
        canUpdateMedia = avatarOwnerId === currentUserId;
      } else if (chat) {
        canUpdateMedia = isChatAdmin(chat);
      }

      isChatWithSelf = selectIsChatWithSelf(global, avatarOwnerId);

      return {
        mediaId,
        senderId: avatarOwnerId,
        avatarOwner: user || chat,
        avatarOwnerFallbackPhoto: user ? selectUserFullInfo(global, avatarOwnerId)?.fallbackPhoto : undefined,
        isChatWithSelf,
        canUpdateMedia,
        withAnimation,
        origin,
        shouldSkipHistoryAnimations,
        isHidden,
      };
    }

    if (!(chatId && threadId && mediaId)) {
      return { withAnimation, shouldSkipHistoryAnimations };
    }

    let message: ApiMessage | undefined;
    if (origin && [MediaViewerOrigin.ScheduledAlbum, MediaViewerOrigin.ScheduledInline].includes(origin)) {
      message = selectScheduledMessage(global, chatId, mediaId);
    } else {
      message = selectChatMessage(global, chatId, mediaId);
    }

    if (!message) {
      return { withAnimation, shouldSkipHistoryAnimations };
    }

    let chatMessages: Record<number, ApiMessage> | undefined;

    if (origin && [MediaViewerOrigin.ScheduledAlbum, MediaViewerOrigin.ScheduledInline].includes(origin)) {
      chatMessages = selectChatScheduledMessages(global, chatId);
    } else {
      chatMessages = selectChatMessages(global, chatId);
    }

    let isLoadingMoreMedia = false;
    const isOriginInline = origin === MediaViewerOrigin.Inline;
    const isOriginAlbum = origin === MediaViewerOrigin.Album;
    let collectionIds: number[] | undefined;

    if (withDynamicLoading && (isOriginInline || isOriginAlbum)) {
      const currentSearch = selectCurrentChatMediaSearch(global);
      isLoadingMoreMedia = Boolean(currentSearch?.isLoading);
      const { foundIds } = (currentSearch?.currentSegment) || {};
      collectionIds = foundIds;
    } else if (origin === MediaViewerOrigin.SharedMedia) {
      const currentSearch = selectCurrentSharedMediaSearch(global);
      const { foundIds } = (currentSearch && currentSearch.resultsByType && currentSearch.resultsByType.media) || {};
      collectionIds = foundIds;
    } else if (isOriginInline || isOriginAlbum) {
      const outlyingList = selectOutlyingListByMessageId(global, chatId, threadId, message.id);
      collectionIds = outlyingList || selectListedIds(global, chatId, threadId);
    }

    return {
      chatId,
      threadId,
      mediaId,
      senderId: message.senderId,
      isChatWithSelf,
      origin,
      message,
      chatMessages,
      collectionIds,
      withAnimation,
      isHidden,
      shouldSkipHistoryAnimations,
      withDynamicLoading,
      isLoadingMoreMedia,
      isSynced,
    };
  },
)(MediaViewer));
