// @flow

import { useFocusEffect, useNavigation } from "@react-navigation/native";
import classnames from "classnames";
import CameraView from "components/Camera/CameraView";
import FadeInOutView from "components/Camera/FadeInOutView";
import useRotation from "components/Camera/hooks/useRotation";
import useZoom from "components/Camera/hooks/useZoom";
import { Pressable, View } from "components/styledComponents";
import { t } from "i18next";
import type { Node } from "react";
import React, {
  useCallback, useEffect,
  useMemo,
  useState,
} from "react";
import DeviceInfo from "react-native-device-info";
import { Snackbar } from "react-native-paper";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { VolumeManager } from "react-native-volume-manager";
import ObservationPhoto from "realmModels/ObservationPhoto";
import { BREAKPOINTS } from "sharedHelpers/breakpoint";
import { log } from "sharedHelpers/logger";
import { useDeviceOrientation, usePerformance } from "sharedHooks";
import useDebugMode from "sharedHooks/useDebugMode";
import useStore from "stores/useStore";

import {
  handleCameraError,
  handleCaptureError,
  handleClassifierError,
  handleDeviceNotSupported,
} from "../helpers";
import CameraNavButtons from "./CameraNavButtons";
import CameraOptionsButtons from "./CameraOptionsButtons";
import DiscardChangesSheet from "./DiscardChangesSheet";
import useBackPress from "./hooks/useBackPress";
import PhotoPreview from "./PhotoPreview";

const logger = log.extend( "StandardCamera" );

const isTablet = DeviceInfo.isTablet( );

export const MAX_PHOTOS_ALLOWED = 20;

type Props = {
  camera: Object,
  device: Object,
  flipCamera: Function,
  handleCheckmarkPress: Function,
  confirmPhotosInProgress: boolean,
  isLandscapeMode: boolean,
  toggleFlash: Function,
  takingPhoto: boolean,
  takePhotoAndStoreUri: Function,
  takePhotoOptions: Object,
  newPhotoUris: Object[],
  setNewPhotoUris: Function
};

const StandardCamera = ( {
  camera,
  device,
  flipCamera,
  handleCheckmarkPress,
  confirmPhotosInProgress,
  isLandscapeMode,
  toggleFlash,
  takingPhoto,
  takePhotoAndStoreUri,
  takePhotoOptions,
  newPhotoUris,
  setNewPhotoUris,
}: Props ): Node => {
  "use no memo";

  const hasFlash = device?.hasFlash;
  const {
    animatedProps,
    handleZoomButtonPress,
    panToZoom,
    pinchToZoom,
    resetZoom,
    showZoomButton,
    zoomTextValue,
  } = useZoom( device );
  const {
    rotatableAnimatedStyle,
    rotation,
  } = useRotation( );
  const navigation = useNavigation( );
  const insets = useSafeAreaInsets();

  const { loadTime } = usePerformance( {
    isLoading: camera?.current !== null,
  } );
  const { isDebug } = useDebugMode();
  useEffect( () => {
    if ( isDebug && loadTime ) {
      logger.info( loadTime );
    }
  }, [isDebug, loadTime] );

  const cameraUris = useStore( state => state.cameraUris );
  const prepareCamera = useStore( state => state.prepareCamera );
  const photoLibraryUris = useStore( state => state.photoLibraryUris );
  const deletePhotoFromObservation = useStore( state => state.deletePhotoFromObservation );

  const totalObsPhotoUris = useMemo(
    ( ) => [...cameraUris, ...photoLibraryUris].length,
    [cameraUris, photoLibraryUris],
  );

  const disallowAddingPhotos = totalObsPhotoUris >= MAX_PHOTOS_ALLOWED;
  const [deletePhotoMode, setDeletePhotoMode] = useState( false );
  const [showAlert, setShowAlert] = useState( false );
  const [initialVolume, setInitialVolume] = useState( null );

  const { screenWidth } = useDeviceOrientation( );

  // newPhotoUris tracks photos taken in *this* instance of the camera. The
  // camera might be instantiated with several cameraUris or
  // photoLibraryUris already in state, but we only want to show the CTA button or discard modal
  // when the user has taken a photo with *this* instance of the camera
  const photosTaken = newPhotoUris.length > 0 && totalObsPhotoUris > 0;
  const {
    handleBackButtonPress,
    setShowDiscardSheet,
    showDiscardSheet,
  } = useBackPress( photosTaken );

  useFocusEffect(
    useCallback( ( ) => {
      // Reset camera zoom every time we get into a fresh camera view
      resetZoom( );
      prepareCamera();
      // TODO: I am not sure how to make the react compiler happy here, so I disabled it
      // for this hook
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [] ),
  );

  const deletePhotoByUri = useCallback( async ( photoUri: string ) => {
    if ( !deletePhotoFromObservation ) return;
    deletePhotoFromObservation( photoUri );
    await ObservationPhoto.deletePhoto( photoUri );
    setNewPhotoUris( newPhotoUris.filter( uri => uri !== photoUri ) );
  }, [deletePhotoFromObservation, newPhotoUris, setNewPhotoUris] );

  const onFlipCamera = () => {
    resetZoom( );
    flipCamera( );
  };

  const containerClasses = ["flex-1"];
  if ( isTablet && isLandscapeMode ) {
    containerClasses.push( "flex-row" );
  }

  const handleDiscard = useCallback( ( ) => {
    newPhotoUris.forEach( uri => {
      deletePhotoByUri( uri );
    } );
    setNewPhotoUris( [] );
    navigation.goBack( );
  }, [deletePhotoByUri, navigation, newPhotoUris, setNewPhotoUris] );

  const handleTakePhoto = useCallback( ( ) => {
    if ( disallowAddingPhotos ) {
      setShowAlert( true );
      return;
    }
    takePhotoAndStoreUri( );
  }, [disallowAddingPhotos, takePhotoAndStoreUri] );

  useEffect( () => {
    if ( initialVolume === null ) {
      // Fetch the current volume to set the initial state
      VolumeManager.getVolume()
        .then( volume => {
          setInitialVolume( volume.volume );
        } );
    }

    const volumeListener = VolumeManager.addVolumeListener( ( ) => {
      if ( initialVolume !== null ) {
        // Hardware volume button pressed - take a photo
        handleTakePhoto();

        // Revert the volume to its previous state
        VolumeManager.setVolume( initialVolume );
      }
    } );

    // Suppress the native volume UI
    VolumeManager.showNativeVolumeUI( { enabled: false } );

    return () => {
      volumeListener.remove();
      VolumeManager.showNativeVolumeUI( { enabled: true } );
    };
  }, [handleTakePhoto, initialVolume] );

  return (
    <View
      className={classnames( containerClasses )}
      style={{ paddingTop: insets.top, paddingBottom: insets.bottom }}
    >
      <PhotoPreview
        deletePhotoMode={deletePhotoMode}
        rotation={rotation}
        setDeletePhotoMode={setDeletePhotoMode}
        takingPhoto={takingPhoto}
        isLandscapeMode={isLandscapeMode}
        isLargeScreen={screenWidth > BREAKPOINTS.md}
        isTablet={isTablet}
        photoUris={cameraUris}
        onDelete={deletePhotoByUri}
      />
      <Pressable
        accessibilityRole="button"
        className="relative flex-1"
        disabled={!deletePhotoMode}
        onPress={() => setDeletePhotoMode( false )}
        pointerEvents={deletePhotoMode
          ? "box-only"
          : "auto"}
      >
        <View className="relative flex-1">
          {device && (
            <CameraView
              animatedProps={animatedProps}
              cameraRef={camera}
              cameraScreen="standard"
              device={device}
              onCameraError={handleCameraError}
              onCaptureError={handleCaptureError}
              onClassifierError={handleClassifierError}
              onDeviceNotSupported={handleDeviceNotSupported}
              panToZoom={panToZoom}
              pinchToZoom={pinchToZoom}
            />
          )}
          <FadeInOutView takingPhoto={takingPhoto} cameraType="Standard" />
          <CameraOptionsButtons
            handleZoomButtonPress={handleZoomButtonPress}
            disabled={disallowAddingPhotos}
            flipCamera={onFlipCamera}
            handleCheckmarkPress={handleCheckmarkPress}
            handleClose={handleBackButtonPress}
            hasFlash={hasFlash}
            photosTaken={photosTaken}
            rotatableAnimatedStyle={rotatableAnimatedStyle}
            showZoomButton={showZoomButton}
            takePhoto={takePhotoAndStoreUri}
            takePhotoOptions={takePhotoOptions}
            toggleFlash={toggleFlash}
            zoomTextValue={zoomTextValue}
          />
        </View>
        <CameraNavButtons
          disabled={disallowAddingPhotos || takingPhoto}
          confirmDisabled={takingPhoto || confirmPhotosInProgress}
          handleCheckmarkPress={handleCheckmarkPress}
          handleClose={handleBackButtonPress}
          photosTaken={photosTaken}
          rotatableAnimatedStyle={rotatableAnimatedStyle}
          takePhoto={handleTakePhoto}
        />
      </Pressable>
      <Snackbar visible={showAlert} onDismiss={() => setShowAlert( false )}>
        {t( "You-can-only-add-20-photos-per-observation" )}
      </Snackbar>
      <DiscardChangesSheet
        setShowDiscardSheet={setShowDiscardSheet}
        hidden={!showDiscardSheet}
        onDiscard={handleDiscard}
      />
    </View>
  );
};

export default StandardCamera;
