import React, {useCallback, useRef, useState} from 'react';
import {
  FlatList,
  NativeScrollEvent,
  NativeSyntheticEvent,
  View,
} from 'react-native';
import {useTranslation} from 'react-i18next';
import {IconDotsVertical, IconDownload} from '@tabler/icons-react-native';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
import {createZoomListComponent, Zoom} from 'react-native-reanimated-zoom';
import {
  BackButton,
  Button,
  Dialog,
  Image,
  ListItem,
  ListRow,
  Pressable,
  Spinner,
} from '@components';
import {Navigation} from '@interfaces/navigation';
import {useMediaAsset, useUserInfo} from '@modules/chats/hooks';
import {IMAGE_SIZE_SMALL_PX} from '@modules/chats/constants';
import {
  IconPlayVideo,
  ProgressiveImage,
  ProgressiveImageHandle,
} from '@modules/chats/components';
import {
  getAttachmentData,
  useAttachmentsIds,
} from '@modules/chats/store/zustand/useAttachments';
import {getChatImage, getUserAvatar, isVideoAsset} from '@modules/chats/utils';
import {
  DOWNLOAD_ERRORS,
  saveAssetToGallery,
} from '@modules/chats/utils/download';
import {useUserId} from '@redux/selectors';
import {showSnackbar} from '@redux/dispatchers';
import {Screens, windowDimensions} from '@shared/constants';
import {SPACING, useTheme} from '@shared/theme';
import {wait} from '@shared/utils';

import {styles} from './styles';
import {VideoPlayer} from './component/VideoPlayer';

const keyExtractor = (item: number) => item.toString();

const getItemLayout = (_: unknown, index: number) => ({
  length: windowDimensions.width,
  offset: windowDimensions.width * index,
  index,
});

const ZoomFlatList = createZoomListComponent(FlatList);

const MainCarouselItem = ({
  assetId,
  messageId,
  isActive,
  autoPlay,
}: {
  assetId: number;
  messageId: string;
  isActive: boolean;
  // `true` only for the very first activation of the video the user
  // opened from chat. `ImageViewer` consumes the initial autoplay the
  // moment the user navigates off it, so swiping back doesn't
  // re-trigger autoplay — same behaviour as any other gallery video
  // from that point on.
  autoPlay: boolean;
}) => {
  const {t} = useTranslation();

  const {isVideo, imageUrl, videoUrl} = useMediaAsset(messageId, assetId);

  // ProgressiveImage auto-retries 3× silently before reporting onError.
  // While a retry is in flight (auto OR manual) we keep the Spinner
  // visible; ProgressiveImage's onLoad/onError settles the state.
  const progressiveRef = useRef<ProgressiveImageHandle>(null);
  const [isRetrying, setIsRetrying] = useState(false);
  const [hasError, setHasError] = useState(false);
  const handleRetry = useCallback(() => {
    setIsRetrying(true);
    setHasError(false);
    progressiveRef.current?.reload().catch(() => {});
  }, []);
  const onImageError = useCallback(() => {
    setIsRetrying(false);
    setHasError(true);
  }, []);
  const onImageLoad = useCallback(() => {
    setIsRetrying(false);
    setHasError(false);
  }, []);

  if (isVideo) {
    return (
      <View style={styles.mainVideoContainer}>
        <VideoPlayer
          url={videoUrl ?? ''}
          thumbnailUrl={imageUrl || undefined}
          messageId={messageId}
          assetId={assetId}
          isActive={isActive}
          autoPlay={autoPlay}
        />
      </View>
    );
  }

  return (
    <View style={styles.mainImageContainer}>
      <Zoom>
        <ProgressiveImage
          ref={progressiveRef}
          url={imageUrl}
          smallWidth={IMAGE_SIZE_SMALL_PX}
          // Gallery viewer wants the full-resolution image; the small
          // variant (already cached from the chat tile) acts as the
          // instant placeholder until the original finishes downloading.
          largeWidth="original"
          contentFit="contain"
          style={styles.mainImage}
          onLoad={onImageLoad}
          onError={onImageError}
        />
      </Zoom>

      {(hasError || isRetrying) && (
        <View style={styles.downloadContainer} pointerEvents="box-none">
          {isRetrying ? (
            <Spinner />
          ) : (
            <Button
              IconRight={IconDownload}
              text={t('general.download')}
              onPress={handleRetry}
              variant="primary"
              size="lg"
            />
          )}
        </View>
      )}
    </View>
  );
};

const SelectorCarouselItem = ({
  assetId,
  messageId,
  onPress,
  isSelected,
}: {
  assetId: number;
  messageId: string;
  onPress: () => void;
  isSelected: boolean;
}) => {
  const {theme} = useTheme();
  const {isVideo, imageUrl} = useMediaAsset(messageId, assetId);
  const thumbUrl = getChatImage.thumbnail(imageUrl, {
    width: IMAGE_SIZE_SMALL_PX,
  });

  return (
    <View style={styles.selectorCarouselItem}>
      <Image
        source={{uri: thumbUrl}}
        loaderType="none"
        onPress={onPress}
        withAuthHeader
        enableCache
        timeout={15_000}
        recyclingKey={thumbUrl}
        // Display target is tiny (70 dp) but the cached variant is
        // bigger — keep the full decoded bitmap so pinch / zoom in the
        // main view doesn't reach for a smaller texture than the
        // selector already has.
        allowDownscaling={false}
        style={[
          styles.selectorCarousel,
          {backgroundColor: theme.background.layout.default},
          isSelected && [
            styles.selectorSelected,
            {borderColor: theme.brand[400]},
          ],
        ]}
      />
      {isVideo && <IconPlayVideo />}
    </View>
  );
};

export function ImageViewer({
  navigation,
  route: {
    params: {initialAssetId, messageId, senderId},
  },
}: Navigation<Screens.CHATS_IMAGE_VIEWER>) {
  const [isDownloading, setIsDownloading] = useState(false);
  const [dialogVisible, setDialogVisible] = useState(false);
  const assetIds = useAttachmentsIds(messageId);
  const [currentAsset, setCurrentAsset] = useState<number | undefined>(
    assetIds.find(assetId => assetId === initialAssetId),
  );
  const initialIndex = assetIds.findIndex(
    assetId => assetId === initialAssetId,
  );
  const {data: user} = useUserInfo(senderId);
  const mainCarouselRef = useRef<FlatList>(null);
  const selectorCarouselRef = useRef<FlatList>(null);
  const {t} = useTranslation();
  const {top, bottom} = useSafeAreaInsets();
  const {theme} = useTheme();
  const userId = useUserId();
  const userAvatar = getUserAvatar(user);
  const isCurrentUser = userId === user?.id;
  const avatar = {
    ...userAvatar,
    title: isCurrentUser ? t('chats.you') : userAvatar.title,
  };
  const isMultipleAssets = assetIds.length > 1;

  // Latches `true` the first time the user navigates off the initial
  // video. After that, the initial autoplay is "spent" — coming back
  // to the same asset should NOT auto-restart, the user has to tap
  // play like on any other video. Without this, every re-activation
  // of the initial asset (since `MainCarouselItem` mounts fresh each
  // time `isActive` flips) would re-fire autoplay.
  const [hasInitialAutoplayed, setHasInitialAutoplayed] = useState(false);

  // Update the highlighted asset only once the paging snap has settled.
  // Doing it on `onScroll` flips `currentAsset` mid-swipe — that
  // propagates through `renderItemMainCarousel`'s deps, re-renders every
  // visible page, and flips the `isActive` prop on the video items. Each
  // flip tears down/spins up `VideoView` (TextureView on Android) and
  // re-runs `useActivePlayback`, which is the lag the user feels while
  // swiping between videos. `onMomentumScrollEnd` fires exactly once
  // when paging settles, so the flip happens after the swipe — not
  // during it.
  const onMomentumScrollEnd = (
    event: NativeSyntheticEvent<NativeScrollEvent>,
  ) => {
    const offsetX = event.nativeEvent.contentOffset.x;
    const index = Math.round(offsetX / windowDimensions.width);
    // Scroll selector carousel to the selected image
    selectorCarouselRef.current?.scrollToIndex({
      index,
      animated: true,
      viewPosition: 0.5,
    });
    const newAsset = assetIds[index];
    setCurrentAsset(newAsset);
    // Consume the initial autoplay the first time the user navigates
    // away from the originally-opened video. Idempotent — only flips
    // once.
    if (newAsset !== initialAssetId && !hasInitialAutoplayed) {
      setHasInitialAutoplayed(true);
    }
  };

  const renderItemMainCarousel = useCallback(
    ({item}: {item: number}) => (
      <MainCarouselItem
        assetId={item}
        messageId={messageId}
        isActive={item === currentAsset}
        autoPlay={item === initialAssetId && !hasInitialAutoplayed}
      />
    ),
    [messageId, currentAsset, initialAssetId, hasInitialAutoplayed],
  );

  const scrollToImage = useCallback((index: number) => {
    if (mainCarouselRef.current) {
      mainCarouselRef.current.scrollToOffset({
        offset: windowDimensions.width * index,
        animated: false,
      });
    }
  }, []);

  const renderItemSelectorCarousel = useCallback(
    ({item, index}: {item: number; index: number}) => {
      const isSelected = item === currentAsset;

      const onPress = () => {
        setCurrentAsset(item);
        scrollToImage(index);
      };

      return (
        <SelectorCarouselItem
          assetId={item}
          messageId={messageId}
          onPress={onPress}
          isSelected={isSelected}
        />
      );
    },
    [currentAsset, messageId, scrollToImage],
  );

  const onPressSettings = () => {
    setDialogVisible(prev => !prev);
  };

  const onCloseDialog = () => {
    setDialogVisible(false);
  };

  const onPressDownload = async () => {
    if (!currentAsset) {
      return;
    }

    const currentAssetData = getAttachmentData(messageId, currentAsset);
    if (!currentAssetData) {
      return;
    }

    try {
      setIsDownloading(true);
      await saveAssetToGallery({
        uri: currentAssetData.uri,
        remoteUrl: currentAssetData.remoteUrl,
        filename: currentAssetData.filename,
        type: isVideoAsset(currentAssetData.asset) ? 'video' : 'photo',
      });
      showSnackbar({
        title: t('general.file_downloaded'),
        variant: 'success',
      });
    } catch (error) {
      if (error === DOWNLOAD_ERRORS.GALLERY_PERMISSION_NOT_GRANTED) {
        showSnackbar({
          title: t('chat.files.missing_permissions.title'),
          description: t('chat.files.missing_permissions.description'),
          variant: 'info',
        });
      } else {
        showSnackbar({
          title: t('errors.api.500', {info: ''}),
          variant: 'error',
        });
      }
    } finally {
      setIsDownloading(false);
      onCloseDialog();
    }
  };

  const onScrollToIndexFailed = async (info: {index: number}) => {
    await wait(500);
    mainCarouselRef.current?.scrollToIndex({
      index: info.index,
      animated: true,
    });
  };

  return (
    <View
      style={[
        styles.container,
        {
          backgroundColor: theme.background.layout.tertiary,
          paddingBottom: bottom || SPACING.x2,
        },
        !!top && {paddingTop: top},
      ]}>
      <Dialog
        title={avatar.title}
        isVisible={dialogVisible}
        onClose={onCloseDialog}>
        <View style={styles.buttonsContainer}>
          <ListRow onPress={onPressDownload}>
            <ListRow.Avatar Icon={IconDownload} />
            <ListRow.Title title={t('general.download')} />
            {isDownloading && <Spinner />}
          </ListRow>
        </View>
      </Dialog>
      <View style={styles.header}>
        <BackButton onPress={navigation.goBack} />
        <View style={styles.headerInfoContainer} accessible={false}>
          <ListItem
            title={avatar.title}
            avatar={avatar}
            style={styles.headerInfo}
            accessible={false}>
            <Pressable
              onPress={onPressSettings}
              // TODO: Need to improve accessibility for this icon button
              accessibilityLabel={t('general.settings')}>
              <IconDotsVertical size={24} color={theme.text.neutral.lighter} />
            </Pressable>
          </ListItem>
        </View>
      </View>
      <ZoomFlatList
        data={assetIds}
        ref={mainCarouselRef}
        initialScrollIndex={initialIndex}
        style={styles.mainListContainer}
        contentContainerStyle={styles.mainListContentContainer}
        onMomentumScrollEnd={onMomentumScrollEnd}
        onScrollToIndexFailed={onScrollToIndexFailed}
        initialNumToRender={assetIds.length}
        getItemLayout={getItemLayout}
        pagingEnabled
        renderItem={renderItemMainCarousel}
        showsHorizontalScrollIndicator={false}
        horizontal
        keyExtractor={keyExtractor}
        scrollEnabled={isMultipleAssets}
        bounces={false}
      />
      {isMultipleAssets && (
        <View style={styles.selectorCarouselContainer}>
          <FlatList
            data={assetIds}
            ref={selectorCarouselRef}
            renderItem={renderItemSelectorCarousel}
            keyExtractor={keyExtractor}
            horizontal
            showsHorizontalScrollIndicator={false}
            bounces={false}
            contentContainerStyle={styles.listPadding}
          />
        </View>
      )}
    </View>
  );
}
