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

import type {
  ApiChat,
  ApiMediaAreaChannelPost,
  ApiPeer, ApiStealthMode, ApiStory, ApiTypeStory,
} from '../../api/types';
import type { IDimensions } from '../../global/types';
import type { Signal } from '../../util/signals';
import { MAIN_THREAD_ID } from '../../api/types';

import { EDITABLE_STORY_INPUT_CSS_SELECTOR, EDITABLE_STORY_INPUT_ID } from '../../config';
import { getSenderTitle, isChatChannel, isUserId } from '../../global/helpers';
import {
  selectChat,
  selectIsCurrentUserPremium,
  selectPeer,
  selectPeerStory,
  selectPerformanceSettingsValue,
  selectTabState,
  selectUser,
} from '../../global/selectors';
import buildClassName from '../../util/buildClassName';
import captureKeyboardListeners from '../../util/captureKeyboardListeners';
import { formatMediaDuration, formatRelativeTime } from '../../util/dates/dateFormat';
import download from '../../util/download';
import { round } from '../../util/math';
import { getServerTime } from '../../util/serverTime';
import { IS_SAFARI } from '../../util/windowEnvironment';
import renderText from '../common/helpers/renderText';
import { BASE_STORY_HEIGHT, BASE_STORY_WIDTH } from './helpers/dimensions';
import { PRIMARY_VIDEO_MIME, SECONDARY_VIDEO_MIME } from './helpers/videoFormats';

import useUnsupportedMedia from '../../hooks/media/useUnsupportedMedia';
import useAppLayout, { getIsMobile } from '../../hooks/useAppLayout';
import useCanvasBlur from '../../hooks/useCanvasBlur';
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
import useFlag from '../../hooks/useFlag';
import useLastCallback from '../../hooks/useLastCallback';
import useLongPress from '../../hooks/useLongPress';
import useMediaTransition from '../../hooks/useMediaTransition';
import useOldLang from '../../hooks/useOldLang';
import useShowTransition from '../../hooks/useShowTransition';
import { useStreaming } from '../../hooks/useStreaming';
import useBackgroundMode from '../../hooks/window/useBackgroundMode';
import useStoryPreloader from './hooks/useStoryPreloader';
import useStoryProps from './hooks/useStoryProps';

import Avatar from '../common/Avatar';
import Composer from '../common/Composer';
import Icon from '../common/icons/Icon';
import Button from '../ui/Button';
import DropdownMenu from '../ui/DropdownMenu';
import MenuItem from '../ui/MenuItem';
import OptimizedVideo from '../ui/OptimizedVideo';
import Skeleton from '../ui/placeholder/Skeleton';
import Transition from '../ui/Transition';
import MediaAreaOverlay from './mediaArea/MediaAreaOverlay';
import StoryCaption from './StoryCaption';
import StoryFooter from './StoryFooter';
import StoryProgress from './StoryProgress';

import styles from './StoryViewer.module.scss';

interface OwnProps {
  peerId: string;
  storyId: number;
  dimensions: IDimensions;
  // eslint-disable-next-line react/no-unused-prop-types
  isReportModalOpen?: boolean;
  // eslint-disable-next-line react/no-unused-prop-types
  isDeleteModalOpen?: boolean;
  isPrivateStories?: boolean;
  isArchivedStories?: boolean;
  isSingleStory?: boolean;
  getIsAnimating: Signal<boolean>;
  onDelete: (story: ApiTypeStory) => void;
  onClose: NoneToVoidFunction;
  onReport: NoneToVoidFunction;
}

interface StateProps {
  peer: ApiPeer;
  forwardSender?: ApiPeer;
  fromPeer?: ApiPeer;
  story?: ApiTypeStory;
  isMuted: boolean;
  orderedIds?: number[];
  shouldForcePause?: boolean;
  storyChangelogUserId?: string;
  viewersExpirePeriod: number;
  isChatExist?: boolean;
  areChatSettingsLoaded?: boolean;
  isCurrentUserPremium?: boolean;
  stealthMode: ApiStealthMode;
  withHeaderAnimation?: boolean;
}

const VIDEO_MIN_READY_STATE = IS_SAFARI ? 4 : 3;
const SPACEBAR_CODE = 32;

const STEALTH_MODE_NOTIFICATION_DURATION = 4000;

function Story({
  peerId,
  storyId,
  peer,
  forwardSender,
  fromPeer,
  isMuted,
  isArchivedStories,
  isPrivateStories,
  story,
  orderedIds,
  isSingleStory,
  dimensions,
  shouldForcePause,
  storyChangelogUserId,
  viewersExpirePeriod,
  isChatExist,
  areChatSettingsLoaded,
  getIsAnimating,
  isCurrentUserPremium,
  stealthMode,
  withHeaderAnimation,
  onDelete,
  onClose,
  onReport,
}: OwnProps & StateProps) {
  const {
    viewStory,
    setStoryViewerMuted,
    openPreviousStory,
    openNextStory,
    loadPeerSkippedStories,
    openForwardMenu,
    copyStoryLink,
    toggleStoryInProfile,
    openChat,
    showNotification,
    openStoryPrivacyEditor,
    loadChatSettings,
    fetchChat,
    loadStoryViews,
    toggleStealthModal,
  } = getActions();
  const serverTime = getServerTime();

  const lang = useOldLang();
  const { isMobile } = useAppLayout();
  const [isComposerHasFocus, markComposerHasFocus, unmarkComposerHasFocus] = useFlag(false);
  const [isStoryPlaybackRequested, playStory, pauseStory] = useFlag(false);
  const [isStoryPlaying, markStoryPlaying, unmarkStoryPlaying] = useFlag(false);
  const [isAppFocused, markAppFocused, unmarkAppFocused] = useFlag(true);
  const [isCaptionExpanded, expandCaption, foldCaption] = useFlag(false);
  const [isPausedBySpacebar, setIsPausedBySpacebar] = useState(false);
  const [isPausedByLongPress, markIsPausedByLongPress, unmarkIsPausedByLongPress] = useFlag(false);
  const [isDropdownMenuOpen, markDropdownMenuOpen, unmarkDropdownMenuOpen] = useFlag(false);
  // eslint-disable-next-line no-null/no-null
  const videoRef = useRef<HTMLVideoElement>(null);
  const {
    isDeletedStory,
    hasText,
    hasForwardInfo,
    thumbnail,
    previewBlobUrl,
    isVideo,
    noSound,
    fullMediaData,
    altMediaHash,
    altMediaData,
    hasFullData,
    hasThumb,
    canDownload,
    downloadMediaData,
  } = useStoryProps(story, isCurrentUserPremium, isDropdownMenuOpen);

  const isLoadedStory = story && 'content' in story;
  const isChangelog = peerId === storyChangelogUserId;
  const isUserStory = isUserId(peerId);
  const isChatStory = !isUserStory;
  const isChannelStory = isChatStory && isChatChannel(peer as ApiChat);
  const isOut = isLoadedStory && story.isOut;

  const canPinToProfile = useCurrentOrPrev(
    isOut ? !story.isInProfile : undefined,
    true,
  );
  const canUnpinFromProfile = useCurrentOrPrev(
    isOut ? story.isInProfile : undefined,
    true,
  );
  const areViewsExpired = Boolean(
    isOut && (story!.date + viewersExpirePeriod) < getServerTime(),
  );

  const forwardSenderTitle = forwardSender ? getSenderTitle(lang, forwardSender)
    : (isLoadedStory && story.forwardInfo?.fromName);

  const canCopyLink = Boolean(
    isLoadedStory
    && story.isPublic
    && !isChangelog
    && peer?.usernames?.length,
  );

  const canShare = Boolean(
    isLoadedStory
    && story.isPublic
    && !story.noForwards
    && !isChangelog
    && !isCaptionExpanded,
  );

  const canPlayStory = Boolean(
    hasFullData && !shouldForcePause && isAppFocused && !isComposerHasFocus && !isCaptionExpanded
    && !isPausedBySpacebar && !isPausedByLongPress,
  );

  const duration = isLoadedStory && story.content.video?.duration
    ? story.content.video.duration
    : undefined;

  const shouldShowComposer = !(isOut && isUserStory) && !isChangelog && !isChannelStory;
  const shouldShowFooter = isLoadedStory && !shouldShowComposer && (isOut || isChannelStory);
  const headerAnimation = isMobile && withHeaderAnimation ? 'slideFade' : 'none';

  const {
    shouldRender: shouldRenderSkeleton,
    transitionClassNames: skeletonTransitionClassNames,
  } = useShowTransition(!hasFullData);

  const {
    transitionClassNames: mediaTransitionClassNames,
  } = useShowTransition(Boolean(fullMediaData));

  const thumbRef = useCanvasBlur(thumbnail, !hasThumb);
  const previewTransitionClassNames = useMediaTransition(previewBlobUrl);

  const {
    shouldRender: shouldRenderComposer,
    transitionClassNames: composerAppearanceAnimationClassNames,
  } = useShowTransition(shouldShowComposer);

  const {
    shouldRender: shouldRenderCaptionBackdrop,
    transitionClassNames: captionBackdropTransitionClassNames,
  } = useShowTransition(hasText && isCaptionExpanded);

  const { transitionClassNames: appearanceAnimationClassNames } = useShowTransition(true);
  const {
    shouldRender: shouldRenderCaption,
    transitionClassNames: captionAppearanceAnimationClassNames,
  } = useShowTransition(hasText || hasForwardInfo);

  const isStreamingSupported = useStreaming(videoRef, fullMediaData, PRIMARY_VIDEO_MIME);

  useStoryPreloader(peerId, storyId);

  useEffect(() => {
    if (storyId) {
      viewStory({ peerId, storyId });
    }
  }, [storyId, peerId]);

  useEffect(() => {
    loadPeerSkippedStories({ peerId });
  }, [peerId]);

  // Fetching user privacy settings for use in Composer
  useEffect(() => {
    const canWrite = isUserId(peerId);
    if (!isChatExist && canWrite) {
      fetchChat({ chatId: peerId });
    }
  }, [isChatExist, peerId]);
  useEffect(() => {
    if (isChatExist && !areChatSettingsLoaded) {
      loadChatSettings({ chatId: peerId });
    }
  }, [areChatSettingsLoaded, isChatExist, peerId]);

  const handlePauseStory = useLastCallback(() => {
    if (isVideo) {
      videoRef.current?.pause();
    }

    unmarkStoryPlaying();
    pauseStory();
  });

  const handlePlayStory = useLastCallback(() => {
    if (!canPlayStory) return;

    playStory();
    if (!isVideo) markStoryPlaying();
  });

  const handleLongPressStart = useLastCallback(() => {
    markIsPausedByLongPress();
  });
  const handleLongPressEnd = useLastCallback(() => {
    unmarkIsPausedByLongPress();
  });

  const handleDropdownMenuOpen = useLastCallback(() => {
    markDropdownMenuOpen();
    handlePauseStory();
  });

  const handleDropdownMenuClose = useLastCallback(() => {
    unmarkDropdownMenuOpen();
    handlePlayStory();
  });

  const {
    onMouseDown: handleLongPressMouseDown,
    onMouseUp: handleLongPressMouseUp,
    onMouseLeave: handleLongPressMouseLeave,
    onTouchStart: handleLongPressTouchStart,
    onTouchEnd: handleLongPressTouchEnd,
  } = useLongPress({
    onStart: handleLongPressStart,
    onEnd: handleLongPressEnd,
  });

  const isUnsupported = useUnsupportedMedia(
    videoRef,
    undefined,
    !isVideo || !fullMediaData || isStreamingSupported,
  );

  const hasAllData = fullMediaData && (!altMediaHash || altMediaData);
  // Play story after media has been downloaded
  useEffect(() => {
    if (hasAllData && !isUnsupported) handlePlayStory();
  }, [hasAllData, isUnsupported]);

  useBackgroundMode(unmarkAppFocused, markAppFocused);

  useEffect(() => {
    if (!hasAllData) return;
    videoRef.current?.load();
  }, [hasAllData]);

  useEffect(() => {
    if (!isLoadedStory || isDeletedStory || areViewsExpired) return;

    if (!isOut && !isChannelStory) return;

    // Refresh counters each time
    loadStoryViews({ peerId, storyId });
  }, [isDeletedStory, areViewsExpired, isLoadedStory, peerId, storyId, isOut, isChannelStory]);

  useEffect(() => {
    if (
      shouldForcePause || !isAppFocused || isComposerHasFocus
      || isCaptionExpanded || isPausedBySpacebar || isPausedByLongPress
    ) {
      handlePauseStory();
    } else {
      handlePlayStory();
    }
  }, [
    handlePlayStory, isAppFocused, isCaptionExpanded, isComposerHasFocus,
    shouldForcePause, isPausedBySpacebar, isPausedByLongPress,
  ]);

  useEffect(() => {
    if (isComposerHasFocus || shouldForcePause || isCaptionExpanded) {
      return undefined;
    }

    function handleKeyDown(e: KeyboardEvent) {
      if (e.keyCode === SPACEBAR_CODE) {
        e.preventDefault();
        setIsPausedBySpacebar(!isPausedBySpacebar);
      }
    }

    document.addEventListener('keydown', handleKeyDown);

    return () => {
      document.removeEventListener('keydown', handleKeyDown);
    };
  }, [isCaptionExpanded, isComposerHasFocus, isPausedBySpacebar, shouldForcePause]);

  // Reset the state of `isPausedBySpacebar` when closing the caption, losing focus by composer or disable forced pause
  useEffectWithPrevDeps(([
    prevIsComposerHasFocus,
    prevIsCaptionExpanded,
    prevShouldForcePause,
    prevIsAppFocused,
    prevIsPausedByLongPress,
  ]) => {
    if (
      !isPausedBySpacebar || isCaptionExpanded || isComposerHasFocus
      || shouldForcePause || !isAppFocused || isPausedByLongPress
    ) {
      return;
    }

    if (
      prevIsCaptionExpanded !== isCaptionExpanded
      || prevIsComposerHasFocus !== isComposerHasFocus
      || prevShouldForcePause !== shouldForcePause
      || prevIsAppFocused !== isAppFocused
      || prevIsPausedByLongPress !== isPausedByLongPress
    ) {
      setIsPausedBySpacebar(false);
    }
  }, [isComposerHasFocus, isCaptionExpanded, shouldForcePause, isAppFocused, isPausedByLongPress, isPausedBySpacebar]);

  const handleOpenChat = useLastCallback(() => {
    onClose();
    openChat({ id: peerId });
  });

  const handleForwardPeerClick = useLastCallback(() => {
    onClose();
    openChat({ id: forwardSender!.id });
  });

  const handleFromPeerClick = useLastCallback(() => {
    onClose();
    openChat({ id: fromPeer!.id });
  });

  const handleOpenPrevStory = useLastCallback(() => {
    openPreviousStory();
  });

  const handleOpenNextStory = useLastCallback(() => {
    openNextStory();
  });

  const handleVideoStoryTimeUpdate = useLastCallback((e: React.SyntheticEvent<HTMLVideoElement>) => {
    const video = e.currentTarget;
    if (video.readyState >= VIDEO_MIN_READY_STATE) {
      markStoryPlaying();
    } else {
      unmarkStoryPlaying();
    }
    if (duration && round(video.currentTime, 2) >= round(duration, 2)) {
      handleOpenNextStory();
    }
  });

  useEffect(() => {
    return !getIsAnimating() && !isComposerHasFocus ? captureKeyboardListeners({
      onRight: handleOpenNextStory,
      onLeft: handleOpenPrevStory,
    }) : undefined;
  }, [getIsAnimating, isComposerHasFocus]);

  const handleCopyStoryLink = useLastCallback(() => {
    copyStoryLink({ peerId, storyId });
  });

  const handlePinClick = useLastCallback(() => {
    toggleStoryInProfile({ peerId, storyId, isInProfile: true });
  });

  const handleUnpinClick = useLastCallback(() => {
    toggleStoryInProfile({ peerId, storyId, isInProfile: false });
  });

  const handleDeleteStoryClick = useLastCallback(() => {
    onDelete(story!);
  });

  const handleReportStoryClick = useLastCallback(() => {
    onReport();
  });

  const handleForwardClick = useLastCallback(() => {
    openForwardMenu({ fromChatId: peerId, storyId });
  });

  const handleInfoPrivacyEdit = useLastCallback(() => {
    openStoryPrivacyEditor();
  });

  const handleInfoPrivacyClick = useLastCallback(() => {
    const visibility = !isLoadedStory || story.isPublic
      ? undefined
      : story.isForContacts ? 'contacts' : (story.isForCloseFriends ? 'closeFriends' : 'nobody');

    let message;
    const myName = getSenderTitle(lang, peer);
    switch (visibility) {
      case 'nobody':
        message = lang('StorySelectedContactsHint', myName);
        break;
      case 'contacts':
        message = lang('StoryContactsHint', myName);
        break;
      case 'closeFriends':
        message = lang('StoryCloseFriendsHint', myName);
        break;
      default:
        return;
    }
    showNotification({ message });
  });

  const handleVolumeMuted = useLastCallback(() => {
    if (noSound) {
      showNotification({
        message: lang('Story.TooltipVideoHasNoSound'),
      });
      return;
    }
    // Browser requires explicit user interaction to keep video playing after unmuting
    videoRef.current!.muted = !videoRef.current!.muted;
    setStoryViewerMuted({ isMuted: !isMuted });
  });

  const handleOpenStealthModal = useLastCallback(() => {
    if (stealthMode.activeUntil && getServerTime() < stealthMode.activeUntil) {
      const diff = stealthMode.activeUntil - getServerTime();
      showNotification({
        title: lang('StealthModeOn'),
        message: lang('Story.ToastStealthModeActiveText', formatMediaDuration(diff)),
        duration: STEALTH_MODE_NOTIFICATION_DURATION,
      });
      return;
    }

    toggleStealthModal({ isOpen: true });
  });

  const handleDownload = useLastCallback(() => {
    if (!downloadMediaData) return;
    download(downloadMediaData, `story-${peerId}-${storyId}.${isVideo ? 'mp4' : 'jpg'}`);
  });

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

    showNotification({
      message: lang('StoryNotFound'),
    });
  }, [lang, isDeletedStory]);

  const MenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
    return ({ onTrigger, isOpen }) => {
      return (
        <Button
          round
          ripple={!isMobile}
          size="tiny"
          color="translucent-white"
          onClick={onTrigger}
          className={buildClassName(styles.button, isOpen && 'active')}
          ariaLabel={lang('AccDescrOpenMenu2')}
        >
          <i className={buildClassName('icon icon-more')} aria-hidden />
        </Button>
      );
    };
  }, [isMobile, lang]);

  function renderStoriesTabs() {
    return (
      <div className={styles.storyIndicators}>
        {(isSingleStory ? [storyId] : orderedIds ?? []).map((id) => (
          <StoryProgress
            key={`progress-${id}`}
            isActive={id === story?.id}
            isVideo={isVideo}
            isViewed={Boolean(story?.id && ((isPrivateStories || isArchivedStories) ? id > story?.id : id < story?.id))}
            isPaused={!isStoryPlaying}
            duration={duration}
            onImageComplete={handleOpenNextStory}
          />
        ))}
      </div>
    );
  }

  function renderStoryPrivacyButton() {
    if (!isUserStory) return undefined;

    let privacyIcon = 'channel-filled';
    const gradient: Record<string, [string, string]> = {
      'channel-filled': ['#50ABFF', '#007AFF'],
      'user-filled': ['#C36EFF', '#8B60FA'],
      'favorite-filled': ['#88D93A', '#30B73B'],
      'group-filled': ['#FFB743', '#F69A36'],
    };

    if (isOut) {
      const { visibility } = (story && 'visibility' in story && story.visibility) || {};

      switch (visibility) {
        case 'everybody':
          privacyIcon = 'channel-filled';
          break;
        case 'contacts':
          privacyIcon = 'user-filled';
          break;
        case 'closeFriends':
          privacyIcon = 'favorite-filled';
          break;
        case 'nobody':
          privacyIcon = 'group-filled';
      }
    } else {
      if (!story || !('content' in story) || story.isPublic) {
        return undefined;
      }

      privacyIcon = story.isForCloseFriends
        ? 'favorite-filled'
        : (story.isForContacts ? 'user-filled' : 'group-filled');
    }

    return (
      <div
        className={buildClassName(styles.visibilityButton, isOut && styles.visibilityButtonSelf)}
        onClick={isOut ? handleInfoPrivacyEdit : handleInfoPrivacyClick}
        style={`--color-from: ${gradient[privacyIcon][0]}; --color-to: ${gradient[privacyIcon][1]}`}
      >
        <i className={`icon icon-${privacyIcon}`} aria-hidden />
        {isOut && <i className="icon icon-next" aria-hidden />}
      </div>
    );
  }

  function renderSenderInfo() {
    return (
      <div className={styles.senderInfo}>
        <Avatar
          peer={peer}
          size="tiny"
          onClick={handleOpenChat}
        />
        <div className={styles.senderMeta}>
          <span onClick={handleOpenChat} className={styles.senderName}>
            {renderText(getSenderTitle(lang, peer) || '')}
          </span>
          <div className={styles.storyMetaRow}>
            {forwardSenderTitle && (
              <span
                className={buildClassName(
                  styles.storyMeta, styles.forwardHeader, forwardSender && styles.clickable,
                )}
                onClick={forwardSender ? handleForwardPeerClick : undefined}
              >
                <Icon name="loop" />
                <span className={styles.headerTitle}>
                  {renderText(forwardSenderTitle)}
                </span>
              </span>
            )}
            {fromPeer && (
              <span
                className={buildClassName(
                  styles.storyMeta, styles.fromPeer,
                )}
                onClick={handleFromPeerClick}
              >
                <Avatar peer={fromPeer} size="micro" />
                <span className={styles.headerTitle}>
                  {renderText(getSenderTitle(lang, fromPeer) || '')}
                </span>
              </span>
            )}
            {story && 'date' in story && (
              <span className={styles.storyMeta}>{formatRelativeTime(lang, serverTime, story.date)}</span>
            )}
            {isLoadedStory && story.isEdited && (
              <span className={styles.storyMeta}>{lang('Story.HeaderEdited')}</span>
            )}
          </div>
        </div>
      </div>
    );
  }

  function renderSender() {
    return (
      <div className={styles.sender}>
        <Transition activeKey={Number(peerId)} name={headerAnimation} className={styles.senderInfoTransition}>
          {renderSenderInfo()}
        </Transition>

        <div className={styles.actions}>
          {renderStoryPrivacyButton()}
          {isVideo && (
            <Button
              className={styles.button}
              round
              ripple={!isMobile}
              size="tiny"
              color="translucent-white"
              disabled={!hasFullData}
              onClick={handleVolumeMuted}
              ariaLabel={lang('Volume')}
            >
              <i
                className={buildClassName(
                  'icon',
                  isMuted || noSound ? 'icon-speaker-muted-story' : 'icon-speaker-story',
                )}
                aria-hidden
              />
            </Button>
          )}
          <DropdownMenu
            className={styles.buttonMenu}
            trigger={MenuButton}
            positionX="right"
            onOpen={handleDropdownMenuOpen}
            onClose={handleDropdownMenuClose}
          >
            {canCopyLink && <MenuItem icon="copy" onClick={handleCopyStoryLink}>{lang('CopyLink')}</MenuItem>}
            {canPinToProfile && (
              <MenuItem icon="save-story" onClick={handlePinClick}>
                {lang(isUserStory ? 'StorySave' : 'SaveToPosts')}
              </MenuItem>
            )}
            {canUnpinFromProfile && (
              <MenuItem icon="delete" onClick={handleUnpinClick}>
                {lang(isUserStory ? 'ArchiveStory' : 'RemoveFromPosts')}
              </MenuItem>
            )}
            {canDownload && (
              <MenuItem icon="download" disabled={!downloadMediaData} onClick={handleDownload}>
                {lang('lng_media_download')}
              </MenuItem>
            )}
            {!isOut && isUserStory && (
              <MenuItem icon="eye-closed-outline" onClick={handleOpenStealthModal}>
                {lang('StealthMode')}
              </MenuItem>
            )}
            {!isOut && <MenuItem icon="flag" onClick={handleReportStoryClick}>{lang('lng_report_story')}</MenuItem>}
            {isOut && <MenuItem icon="delete" destructive onClick={handleDeleteStoryClick}>{lang('Delete')}</MenuItem>}
          </DropdownMenu>
          <Button
            className={buildClassName(styles.button, styles.closeButton)}
            round
            size="tiny"
            color="translucent-white"
            ariaLabel={lang('Close')}
            onClick={onClose}
          >
            <i className={buildClassName('icon icon-close')} aria-hidden />
          </Button>
        </div>
      </div>
    );
  }

  return (
    <div
      className={buildClassName(styles.slideInner, 'component-theme-dark')}
      onMouseDown={handleLongPressMouseDown}
      onMouseUp={handleLongPressMouseUp}
      onMouseLeave={handleLongPressMouseLeave}
      onTouchStart={handleLongPressTouchStart}
      onTouchEnd={handleLongPressTouchEnd}
    >
      <div className={buildClassName(styles.storyHeader, appearanceAnimationClassNames)}>
        {renderStoriesTabs()}
        {renderSender()}
      </div>

      <div
        className={styles.mediaWrapper}
        style={`width: ${dimensions.width}px; height: ${dimensions.height}px`}
      >
        <canvas ref={thumbRef} className={styles.thumbnail} />
        {previewBlobUrl && (
          <img
            key={`preview-${storyId}`}
            src={previewBlobUrl}
            draggable={false}
            alt=""
            className={buildClassName(styles.media, styles.mediaPreview, previewTransitionClassNames)}
          />
        )}
        {shouldRenderSkeleton && (
          <Skeleton className={buildClassName(skeletonTransitionClassNames, styles.fullSize)} />
        )}
        {!isVideo && fullMediaData && (
          <img
            src={fullMediaData}
            alt=""
            className={buildClassName(styles.media, mediaTransitionClassNames)}
            draggable={false}
          />
        )}
        {isVideo && fullMediaData && (
          <OptimizedVideo
            ref={videoRef}
            key={`video-${storyId}`}
            className={buildClassName(styles.media, mediaTransitionClassNames)}
            canPlay={isStoryPlaybackRequested}
            muted={isMuted}
            width={BASE_STORY_WIDTH}
            height={BASE_STORY_HEIGHT}
            draggable={false}
            playsInline
            disablePictureInPicture
            isPriority
            onPause={unmarkStoryPlaying}
            onWaiting={unmarkStoryPlaying}
            disableRemotePlayback
            onTimeUpdate={handleVideoStoryTimeUpdate}
          >
            <source src={fullMediaData} type={PRIMARY_VIDEO_MIME} width="720" />
            {altMediaData && <source src={altMediaData} type={SECONDARY_VIDEO_MIME} width="480" />}
          </OptimizedVideo>
        )}

        {!isPausedByLongPress && !isComposerHasFocus && (
          <>
            <button
              type="button"
              className={buildClassName(styles.navigate, styles.prev)}
              onClick={handleOpenPrevStory}
              aria-label={lang('Previous')}
            />
            <button
              type="button"
              className={buildClassName(styles.navigate, styles.next)}
              onClick={handleOpenNextStory}
              aria-label={lang('Next')}
            />
          </>
        )}
        {isLoadedStory && fullMediaData && (
          <MediaAreaOverlay story={story} isActive />
        )}
        {!isMobile && (
          <div className={styles.content}>
            <div className={styles.contentInner}>
              <Avatar
                peer={peer}
                withStory
                storyViewerMode="disabled"
              />
              <div className={styles.name}>{renderText(getSenderTitle(lang, peer) || '')}</div>
            </div>
          </div>
        )}
      </div>

      {shouldShowFooter && (
        <StoryFooter story={story} className={appearanceAnimationClassNames} />
      )}
      {shouldRenderCaptionBackdrop && (
        <div
          tabIndex={0}
          role="button"
          className={buildClassName(styles.captionBackdrop, captionBackdropTransitionClassNames)}
          onClick={() => foldCaption()}
          aria-label={lang('Close')}
        />
      )}
      {hasText && <div className={buildClassName(styles.captionGradient, captionAppearanceAnimationClassNames)} />}
      {shouldRenderCaption && (
        <StoryCaption
          key={`caption-${storyId}-${peerId}`}
          story={story as ApiStory}
          isExpanded={isCaptionExpanded}
          onExpand={expandCaption}
          onFold={foldCaption}
          className={captionAppearanceAnimationClassNames}
        />
      )}
      {shouldRenderComposer && (
        <Composer
          type="story"
          chatId={peerId}
          threadId={MAIN_THREAD_ID}
          storyId={storyId}
          isReady={!isOut}
          messageListType="thread"
          isMobile={getIsMobile()}
          editableInputCssSelector={EDITABLE_STORY_INPUT_CSS_SELECTOR}
          editableInputId={EDITABLE_STORY_INPUT_ID}
          inputId="story-input-text"
          className={buildClassName(styles.composer, composerAppearanceAnimationClassNames)}
          inputPlaceholder={lang(isChatStory ? 'ReplyToGroupStory' : 'ReplyPrivately')}
          onForward={canShare ? handleForwardClick : undefined}
          onFocus={markComposerHasFocus}
          onBlur={unmarkComposerHasFocus}
        />
      )}
    </div>
  );
}

export default memo(withGlobal<OwnProps>((global, {
  peerId,
  storyId,
  isReportModalOpen,
  isDeleteModalOpen,
}): StateProps => {
  const { appConfig } = global;
  const user = selectUser(global, peerId);
  const chat = selectChat(global, peerId);
  const tabState = selectTabState(global);
  const {
    storyViewer: {
      isMuted,
      viewModal,
      isPrivacyModalOpen,
      isStealthModalOpen,
      storyList,
    },
    forwardMessages: { storyId: forwardedStoryId },
    premiumModal,
    safeLinkModalUrl,
    mapModal,
  } = tabState;
  const { isOpen: isPremiumModalOpen } = premiumModal || {};
  const story = selectPeerStory(global, peerId, storyId);
  const isLoadedStory = story && 'content' in story;
  const shouldForcePause = Boolean(
    viewModal || forwardedStoryId || tabState.reactionPicker?.storyId || isReportModalOpen || isPrivacyModalOpen
    || isPremiumModalOpen || isDeleteModalOpen || safeLinkModalUrl || isStealthModalOpen || mapModal,
  );

  const forwardInfo = isLoadedStory ? story.forwardInfo : undefined;
  const mediaAreas = isLoadedStory ? story.mediaAreas : undefined;
  const forwardSenderId = forwardInfo?.fromPeerId
    || mediaAreas?.find((area): area is ApiMediaAreaChannelPost => area.type === 'channelPost')?.channelId;
  const forwardSender = forwardSenderId ? selectPeer(global, forwardSenderId) : undefined;
  const withHeaderAnimation = selectPerformanceSettingsValue(global, 'mediaViewerAnimations');

  const fromPeer = isLoadedStory && story.fromId ? selectPeer(global, story.fromId) : undefined;

  return {
    peer: (user || chat)!,
    forwardSender,
    fromPeer,
    story,
    orderedIds: storyList?.storyIdsByPeerId[peerId],
    isMuted,
    isCurrentUserPremium: selectIsCurrentUserPremium(global),
    shouldForcePause,
    storyChangelogUserId: appConfig!.storyChangelogUserId,
    viewersExpirePeriod: appConfig!.storyExpirePeriod + appConfig!.storyViewersExpirePeriod,
    isChatExist: Boolean(chat),
    areChatSettingsLoaded: Boolean(chat?.settings),
    stealthMode: global.stories.stealthMode,
    withHeaderAnimation,
  };
})(Story));
