import React, {
  useState,
  useCallback,
  useMemo,
  useRef,
  forwardRef,
  useImperativeHandle,
} from 'react';
import type {ElementRef} from 'react';
import {View, StyleSheet, Image, Platform, processColor} from 'react-native';
import type {
  StyleProp,
  ImageStyle,
  NativeSyntheticEvent,
  ViewStyle,
  ImageResizeMode,
} from 'react-native';

import NativeVideoComponent, {
  NativeCmcdConfiguration,
} from './specs/VideoNativeComponent';
import type {
  OnAudioFocusChangedData,
  OnAudioTracksData,
  OnBandwidthUpdateData,
  OnBufferData,
  OnControlsVisibilityChange,
  OnExternalPlaybackChangeData,
  OnGetLicenseData,
  OnLoadStartData,
  OnPictureInPictureStatusChangedData,
  OnPlaybackStateChangedData,
  OnProgressData,
  OnSeekData,
  OnSeekCompleteData,
  OnTextTrackDataChangedData,
  OnTimedMetadataData,
  OnVideoAspectRatioData,
  OnVideoErrorData,
  OnVideoTracksData,
  VideoSrc,
} from './specs/VideoNativeComponent';
import {
  generateHeaderForNative,
  getReactTag,
  resolveAssetSourceForVideo,
} from './utils';
import NativeVideoManager from './specs/NativeVideoManager';
import {type VideoSaveData, CmcdMode, ViewType} from './types';
import type {
  OnLoadData,
  OnTextTracksData,
  OnReceiveAdEventData,
  ReactVideoProps,
  CmcdData,
  ReactVideoSource,
} from './types';

export interface VideoRef {
  seek: (time: number, tolerance?: number) => void;
  resume: () => void;
  pause: () => void;
  presentFullscreenPlayer: () => void;
  dismissFullscreenPlayer: () => void;
  restoreUserInterfaceForPictureInPictureStopCompleted: (
    restore: boolean,
  ) => void;
  setVolume: (volume: number) => void;
  setFullScreen: (fullScreen: boolean) => void;
  setSource: (source?: ReactVideoSource) => void;
  save: (options: object) => Promise<VideoSaveData> | void;
  getCurrentPosition: () => Promise<number>;
}

const Video = forwardRef<VideoRef, ReactVideoProps>(
  (
    {
      source,
      style,
      resizeMode,
      poster,
      posterResizeMode,
      renderLoader,
      contentStartTime,
      drm,
      textTracks,
      selectedVideoTrack,
      selectedAudioTrack,
      selectedTextTrack,
      useTextureView,
      useSecureView,
      viewType,
      shutterColor,
      adTagUrl,
      adLanguage,
      onLoadStart,
      onLoad,
      onError,
      onProgress,
      onSeek,
      onSeekComplete,
      onEnd,
      onBuffer,
      onBandwidthUpdate,
      onControlsVisibilityChange,
      onExternalPlaybackChange,
      onFullscreenPlayerWillPresent,
      onFullscreenPlayerDidPresent,
      onFullscreenPlayerWillDismiss,
      onFullscreenPlayerDidDismiss,
      onReadyForDisplay,
      onPlaybackRateChange,
      onVolumeChange,
      onAudioBecomingNoisy,
      onPictureInPictureStatusChanged,
      onRestoreUserInterfaceForPictureInPictureStop,
      onReceiveAdEvent,
      onPlaybackStateChanged,
      onAudioFocusChanged,
      onIdle,
      onTimedMetadata,
      onAudioTracks,
      onTextTracks,
      onTextTrackDataChanged,
      onVideoTracks,
      onAspectRatio,
      localSourceEncryptionKeyScheme,
      ...rest
    },
    ref,
  ) => {
    const nativeRef = useRef<ElementRef<typeof NativeVideoComponent>>(null);

    const isPosterDeprecated = typeof poster === 'string';

    const _renderLoader = useMemo(
      () =>
        !renderLoader
          ? undefined
          : renderLoader instanceof Function
          ? renderLoader
          : () => renderLoader,
      [renderLoader],
    );

    const hasPoster = useMemo(() => {
      if (_renderLoader) {
        return true;
      }

      if (isPosterDeprecated) {
        return !!poster;
      }

      return !!poster?.source;
    }, [isPosterDeprecated, poster, _renderLoader]);

    const [showPoster, setShowPoster] = useState(hasPoster);

    const [
      _restoreUserInterfaceForPIPStopCompletionHandler,
      setRestoreUserInterfaceForPIPStopCompletionHandler,
    ] = useState<boolean | undefined>();

    const sourceToUnternalSource = useCallback(
      (_source?: ReactVideoSource) => {
        if (!_source) {
          return undefined;
        }
        const resolvedSource = resolveAssetSourceForVideo(_source);
        let uri = resolvedSource.uri || '';
        if (uri && uri.match(/^\//)) {
          uri = `file://${uri}`;
        }
        if (!uri) {
          console.log('Trying to load empty source');
        }
        const isNetwork = !!(uri && uri.match(/^(rtp|rtsp|http|https):/));
        const isAsset = !!(
          uri &&
          uri.match(
            /^(assets-library|ipod-library|file|content|ms-appx|ms-appdata):/,
          )
        );

        const selectedDrm = _source.drm || drm;
        const _textTracks = _source.textTracks || textTracks;
        const _drm = !selectedDrm
          ? undefined
          : {
              type: selectedDrm.type,
              licenseServer: selectedDrm.licenseServer,
              headers: generateHeaderForNative(selectedDrm.headers),
              contentId: selectedDrm.contentId,
              certificateUrl: selectedDrm.certificateUrl,
              base64Certificate: selectedDrm.base64Certificate,
              useExternalGetLicense: !!selectedDrm.getLicense,
              multiDrm: selectedDrm.multiDrm,
              localSourceEncryptionKeyScheme:
                selectedDrm.localSourceEncryptionKeyScheme ||
                localSourceEncryptionKeyScheme,
            };

        let _cmcd: NativeCmcdConfiguration | undefined;
        if (Platform.OS === 'android' && source?.cmcd) {
          const cmcd = source.cmcd;

          if (typeof cmcd === 'boolean') {
            _cmcd = cmcd ? {mode: CmcdMode.MODE_QUERY_PARAMETER} : undefined;
          } else if (typeof cmcd === 'object' && !Array.isArray(cmcd)) {
            const createCmcdHeader = (property?: CmcdData) =>
              property ? generateHeaderForNative(property) : undefined;

            _cmcd = {
              mode: cmcd.mode ?? CmcdMode.MODE_QUERY_PARAMETER,
              request: createCmcdHeader(cmcd.request),
              session: createCmcdHeader(cmcd.session),
              object: createCmcdHeader(cmcd.object),
              status: createCmcdHeader(cmcd.status),
            };
          } else {
            throw new Error(
              'Invalid CMCD configuration: Expected a boolean or an object.',
            );
          }
        }

        const selectedContentStartTime =
          _source.contentStartTime || contentStartTime;

        const _ad =
          _source.ad ||
          (adTagUrl || adLanguage
            ? {adTagUrl: adTagUrl, adLanguage: adLanguage}
            : undefined);

        return {
          uri,
          isNetwork,
          isAsset,
          shouldCache: resolvedSource.shouldCache || false,
          type: resolvedSource.type || '',
          mainVer: resolvedSource.mainVer || 0,
          patchVer: resolvedSource.patchVer || 0,
          requestHeaders: generateHeaderForNative(resolvedSource.headers),
          startPosition: resolvedSource.startPosition ?? -1,
          cropStart: resolvedSource.cropStart || 0,
          cropEnd: resolvedSource.cropEnd,
          contentStartTime: selectedContentStartTime,
          metadata: resolvedSource.metadata,
          drm: _drm,
          ad: _ad,
          cmcd: _cmcd,
          textTracks: _textTracks,
          textTracksAllowChunklessPreparation:
            resolvedSource.textTracksAllowChunklessPreparation,
        };
      },
      [
        adLanguage,
        adTagUrl,
        contentStartTime,
        drm,
        localSourceEncryptionKeyScheme,
        source?.cmcd,
        textTracks,
      ],
    );

    const src = useMemo<VideoSrc | undefined>(() => {
      return sourceToUnternalSource(source);
    }, [sourceToUnternalSource, source]);

    const _selectedTextTrack = useMemo(() => {
      if (!selectedTextTrack) {
        return;
      }
      const typeOfValueProp = typeof selectedTextTrack.value;
      if (
        typeOfValueProp !== 'number' &&
        typeOfValueProp !== 'string' &&
        typeOfValueProp !== 'undefined'
      ) {
        console.warn(
          'invalid type provided to selectedTextTrack.value: ',
          typeOfValueProp,
        );
        return;
      }
      return {
        type: selectedTextTrack?.type,
        value: `${selectedTextTrack.value}`,
      };
    }, [selectedTextTrack]);

    const _selectedAudioTrack = useMemo(() => {
      if (!selectedAudioTrack) {
        return;
      }
      const typeOfValueProp = typeof selectedAudioTrack.value;
      if (
        typeOfValueProp !== 'number' &&
        typeOfValueProp !== 'string' &&
        typeOfValueProp !== 'undefined'
      ) {
        console.warn(
          'invalid type provided to selectedAudioTrack.value: ',
          typeOfValueProp,
        );
        return;
      }

      return {
        type: selectedAudioTrack?.type,
        value: `${selectedAudioTrack.value}`,
      };
    }, [selectedAudioTrack]);

    const _selectedVideoTrack = useMemo(() => {
      if (!selectedVideoTrack) {
        return;
      }
      const typeOfValueProp = typeof selectedVideoTrack.value;
      if (
        typeOfValueProp !== 'number' &&
        typeOfValueProp !== 'string' &&
        typeOfValueProp !== 'undefined'
      ) {
        console.warn(
          'invalid type provided to selectedVideoTrack.value: ',
          typeOfValueProp,
        );
        return;
      }
      return {
        type: selectedVideoTrack?.type,
        value: `${selectedVideoTrack.value}`,
      };
    }, [selectedVideoTrack]);

    const seek = useCallback(async (time: number, tolerance?: number) => {
      if (isNaN(time) || time === null) {
        throw new Error("Specified time is not a number: '" + time + "'");
      }

      if (!nativeRef.current) {
        console.warn('Video Component is not mounted');
        return;
      }

      const callSeekFunction = () => {
        NativeVideoManager.seekCmd(
          getReactTag(nativeRef),
          time,
          tolerance || 0,
        );
      };

      Platform.select({
        ios: callSeekFunction,
        android: callSeekFunction,
        default: () => {
          // TODO: Implement VideoManager.seekCmd for windows
          nativeRef.current?.setNativeProps({seek: time});
        },
      })();
    }, []);

    const pause = useCallback(() => {
      return NativeVideoManager.setPlayerPauseStateCmd(
        getReactTag(nativeRef),
        true,
      );
    }, []);

    const resume = useCallback(() => {
      return NativeVideoManager.setPlayerPauseStateCmd(
        getReactTag(nativeRef),
        false,
      );
    }, []);

    const setVolume = useCallback((volume: number) => {
      return NativeVideoManager.setVolumeCmd(getReactTag(nativeRef), volume);
    }, []);

    const setFullScreen = useCallback((fullScreen: boolean) => {
      return NativeVideoManager.setFullScreenCmd(
        getReactTag(nativeRef),
        fullScreen,
      );
    }, []);

    const setSource = useCallback(
      (_source?: ReactVideoSource) => {
        return NativeVideoManager.setSourceCmd(
          getReactTag(nativeRef),
          sourceToUnternalSource(_source),
        );
      },
      [sourceToUnternalSource],
    );

    const presentFullscreenPlayer = useCallback(
      () => setFullScreen(true),
      [setFullScreen],
    );

    const dismissFullscreenPlayer = useCallback(
      () => setFullScreen(false),
      [setFullScreen],
    );

    const save = useCallback((options: object) => {
      // VideoManager.save can be null on android & windows
      if (Platform.OS !== 'ios') {
        return;
      }
      // @todo Must implement it in a different way.
      return NativeVideoManager.save?.(getReactTag(nativeRef), options);
    }, []);

    const getCurrentPosition = useCallback(() => {
      // @todo Must implement it in a different way.
      return NativeVideoManager.getCurrentPosition(getReactTag(nativeRef));
    }, []);

    const restoreUserInterfaceForPictureInPictureStopCompleted = useCallback(
      (restored: boolean) => {
        setRestoreUserInterfaceForPIPStopCompletionHandler(restored);
      },
      [setRestoreUserInterfaceForPIPStopCompletionHandler],
    );

    const onVideoLoadStart = useCallback(
      (e: NativeSyntheticEvent<OnLoadStartData>) => {
        hasPoster && setShowPoster(true);
        onLoadStart?.(e.nativeEvent);
      },
      [hasPoster, onLoadStart],
    );

    const onVideoLoad = useCallback(
      (e: NativeSyntheticEvent<OnLoadData>) => {
        if (Platform.OS === 'windows') {
          hasPoster && setShowPoster(false);
        }
        onLoad?.(e.nativeEvent);
      },
      [onLoad, hasPoster, setShowPoster],
    );

    const onVideoError = useCallback(
      (e: NativeSyntheticEvent<OnVideoErrorData>) => {
        onError?.(e.nativeEvent);
      },
      [onError],
    );

    const onVideoProgress = useCallback(
      (e: NativeSyntheticEvent<OnProgressData>) => {
        onProgress?.(e.nativeEvent);
      },
      [onProgress],
    );

    const onVideoSeek = useCallback(
      (e: NativeSyntheticEvent<OnSeekData>) => {
        onSeek?.(e.nativeEvent);
      },
      [onSeek],
    );

    const onVideoSeekComplete = useCallback(
      (e: NativeSyntheticEvent<OnSeekCompleteData>) => {
        onSeekComplete?.(e.nativeEvent);
      },
      [onSeekComplete]
    );

    const onVideoPlaybackStateChanged = useCallback(
      (e: NativeSyntheticEvent<OnPlaybackStateChangedData>) => {
        onPlaybackStateChanged?.(e.nativeEvent);
      },
      [onPlaybackStateChanged],
    );

    const _shutterColor = useMemo(() => {
      const color = processColor(shutterColor);
      return typeof color === 'number' ? color : undefined;
    }, [shutterColor]);

    // android only
    const _onTimedMetadata = useCallback(
      (e: NativeSyntheticEvent<OnTimedMetadataData>) => {
        onTimedMetadata?.(e.nativeEvent);
      },
      [onTimedMetadata],
    );

    const _onAudioTracks = useCallback(
      (e: NativeSyntheticEvent<OnAudioTracksData>) => {
        onAudioTracks?.(e.nativeEvent);
      },
      [onAudioTracks],
    );

    const _onTextTracks = useCallback(
      (e: NativeSyntheticEvent<OnTextTracksData>) => {
        onTextTracks?.(e.nativeEvent);
      },
      [onTextTracks],
    );

    const _onTextTrackDataChanged = useCallback(
      (
        e: NativeSyntheticEvent<OnTextTrackDataChangedData & {target?: number}>,
      ) => {
        const {...eventData} = e.nativeEvent;
        delete eventData.target;
        onTextTrackDataChanged?.(eventData as OnTextTrackDataChangedData);
      },
      [onTextTrackDataChanged],
    );

    const _onVideoTracks = useCallback(
      (e: NativeSyntheticEvent<OnVideoTracksData>) => {
        onVideoTracks?.(e.nativeEvent);
      },
      [onVideoTracks],
    );

    const _onPlaybackRateChange = useCallback(
      (e: NativeSyntheticEvent<Readonly<{playbackRate: number}>>) => {
        onPlaybackRateChange?.(e.nativeEvent);
      },
      [onPlaybackRateChange],
    );

    const _onVolumeChange = useCallback(
      (e: NativeSyntheticEvent<Readonly<{volume: number}>>) => {
        onVolumeChange?.(e.nativeEvent);
      },
      [onVolumeChange],
    );

    const _onReadyForDisplay = useCallback(() => {
      hasPoster && setShowPoster(false);
      onReadyForDisplay?.();
    }, [setShowPoster, hasPoster, onReadyForDisplay]);

    const _onPictureInPictureStatusChanged = useCallback(
      (e: NativeSyntheticEvent<OnPictureInPictureStatusChangedData>) => {
        onPictureInPictureStatusChanged?.(e.nativeEvent);
      },
      [onPictureInPictureStatusChanged],
    );

    const _onAudioFocusChanged = useCallback(
      (e: NativeSyntheticEvent<OnAudioFocusChangedData>) => {
        onAudioFocusChanged?.(e.nativeEvent);
      },
      [onAudioFocusChanged],
    );

    const onVideoBuffer = useCallback(
      (e: NativeSyntheticEvent<OnBufferData>) => {
        onBuffer?.(e.nativeEvent);
      },
      [onBuffer],
    );

    const onVideoExternalPlaybackChange = useCallback(
      (e: NativeSyntheticEvent<OnExternalPlaybackChangeData>) => {
        onExternalPlaybackChange?.(e.nativeEvent);
      },
      [onExternalPlaybackChange],
    );

    const _onBandwidthUpdate = useCallback(
      (e: NativeSyntheticEvent<OnBandwidthUpdateData>) => {
        onBandwidthUpdate?.(e.nativeEvent);
      },
      [onBandwidthUpdate],
    );

    const _onReceiveAdEvent = useCallback(
      (e: NativeSyntheticEvent<OnReceiveAdEventData>) => {
        onReceiveAdEvent?.(e.nativeEvent);
      },
      [onReceiveAdEvent],
    );

    const _onVideoAspectRatio = useCallback(
      (e: NativeSyntheticEvent<OnVideoAspectRatioData>) => {
        onAspectRatio?.(e.nativeEvent);
      },
      [onAspectRatio],
    );

    const _onControlsVisibilityChange = useCallback(
      (e: NativeSyntheticEvent<OnControlsVisibilityChange>) => {
        onControlsVisibilityChange?.(e.nativeEvent);
      },
      [onControlsVisibilityChange],
    );

    const selectedDrm = source?.drm || drm;
    const usingExternalGetLicense = selectedDrm?.getLicense instanceof Function;

    const onGetLicense = useCallback(
      async (event: NativeSyntheticEvent<OnGetLicenseData>) => {
        if (!usingExternalGetLicense) {
          return;
        }
        const data = event.nativeEvent;
        try {
          if (!data?.spcBase64) {
            throw new Error('No spc received');
          }
          // Handles both scenarios, getLicenseOverride being a promise and not.
          const license = await Promise.resolve(
            selectedDrm.getLicense(
              data.spcBase64,
              data.contentId,
              data.licenseUrl,
              data.loadedLicenseUrl,
            ),
          ).catch(() => {
            throw new Error('fetch error');
          });
          if (typeof license !== 'string') {
            throw Error('Empty license result');
          }
          if (nativeRef.current) {
            NativeVideoManager.setLicenseResultCmd(
              getReactTag(nativeRef),
              license,
              data.loadedLicenseUrl,
            );
          }
        } catch (e) {
          const msg = e instanceof Error ? e.message : 'fetch error';
          if (nativeRef.current) {
            NativeVideoManager.setLicenseResultErrorCmd(
              getReactTag(nativeRef),
              msg,
              data.loadedLicenseUrl,
            );
          }
        }
      },
      [selectedDrm, usingExternalGetLicense],
    );

    useImperativeHandle(
      ref,
      () => ({
        seek,
        presentFullscreenPlayer,
        dismissFullscreenPlayer,
        save,
        pause,
        resume,
        restoreUserInterfaceForPictureInPictureStopCompleted,
        setVolume,
        getCurrentPosition,
        setFullScreen,
        setSource,
      }),
      [
        seek,
        presentFullscreenPlayer,
        dismissFullscreenPlayer,
        save,
        pause,
        resume,
        restoreUserInterfaceForPictureInPictureStopCompleted,
        setVolume,
        getCurrentPosition,
        setFullScreen,
        setSource,
      ],
    );

    const _viewType = useMemo(() => {
      const hasValidDrmProp =
        drm !== undefined && Object.keys(drm).length !== 0;

      const shallForceViewType =
        hasValidDrmProp && (viewType === ViewType.TEXTURE || useTextureView);

      if (useSecureView && useTextureView) {
        console.warn(
          'cannot use SecureView on texture view. please set useTextureView={false}',
        );
      }

      if (shallForceViewType) {
        console.warn(
          'cannot use DRM on texture view. please set useTextureView={false}',
        );
        return useSecureView ? ViewType.SURFACE_SECURE : ViewType.SURFACE;
      }

      if (viewType !== undefined && viewType !== null) {
        return viewType;
      }

      if (useSecureView) {
        return ViewType.SURFACE_SECURE;
      }

      if (useTextureView) {
        return ViewType.TEXTURE;
      }

      return ViewType.SURFACE;
    }, [drm, useSecureView, useTextureView, viewType]);

    const _renderPoster = useCallback(() => {
      if (!hasPoster || !showPoster) {
        return null;
      }

      // poster resize mode
      let _posterResizeMode: ImageResizeMode = 'contain';

      if (!isPosterDeprecated && poster?.resizeMode) {
        _posterResizeMode = poster.resizeMode;
      } else if (posterResizeMode && posterResizeMode !== 'none') {
        _posterResizeMode = posterResizeMode;
      }

      // poster style
      const baseStyle: StyleProp<ImageStyle> = {
        ...StyleSheet.absoluteFillObject,
        resizeMode: _posterResizeMode,
      };

      let posterStyle: StyleProp<ImageStyle> = baseStyle;

      if (!isPosterDeprecated && poster?.style) {
        const styles = Array.isArray(poster.style)
          ? poster.style
          : [poster.style];
        posterStyle = [baseStyle, ...styles];
      }

      // render poster
      if (_renderLoader && (poster || posterResizeMode)) {
        console.warn(
          'You provided both `renderLoader` and `poster` or `posterResizeMode` props. `renderLoader` will be used.',
        );
      }

      // render loader
      if (_renderLoader) {
        return (
          <View style={StyleSheet.absoluteFill}>
            {_renderLoader({
              source: source,
              style: posterStyle,
              resizeMode: resizeMode,
            })}
          </View>
        );
      }

      return (
        <Image
          {...(isPosterDeprecated ? {} : poster)}
          source={isPosterDeprecated ? {uri: poster} : poster?.source}
          style={posterStyle}
        />
      );
    }, [
      hasPoster,
      isPosterDeprecated,
      poster,
      posterResizeMode,
      _renderLoader,
      showPoster,
      source,
      resizeMode,
    ]);

    const _style: StyleProp<ViewStyle> = useMemo(
      () => ({
        ...StyleSheet.absoluteFillObject,
        ...(showPoster ? {display: 'none'} : {}),
      }),
      [showPoster],
    );

    return (
      <View style={style}>
        <NativeVideoComponent
          ref={nativeRef}
          {...rest}
          src={src}
          style={_style}
          resizeMode={resizeMode}
          restoreUserInterfaceForPIPStopCompletionHandler={
            _restoreUserInterfaceForPIPStopCompletionHandler
          }
          selectedTextTrack={_selectedTextTrack}
          selectedAudioTrack={_selectedAudioTrack}
          selectedVideoTrack={_selectedVideoTrack}
          shutterColor={_shutterColor}
          onGetLicense={usingExternalGetLicense ? onGetLicense : undefined}
          onVideoLoad={
            onLoad || hasPoster
              ? (onVideoLoad as (e: NativeSyntheticEvent<object>) => void)
              : undefined
          }
          onVideoLoadStart={
            onLoadStart || hasPoster ? onVideoLoadStart : undefined
          }
          onVideoError={onError ? onVideoError : undefined}
          onVideoProgress={onProgress ? onVideoProgress : undefined}
          onVideoSeek={onSeek ? onVideoSeek : undefined}
          onVideoSeekComplete={onSeekComplete ? onVideoSeekComplete : undefined}
          onVideoEnd={onEnd}
          onVideoBuffer={onBuffer ? onVideoBuffer : undefined}
          onVideoPlaybackStateChanged={
            onPlaybackStateChanged ? onVideoPlaybackStateChanged : undefined
          }
          onVideoBandwidthUpdate={
            onBandwidthUpdate ? _onBandwidthUpdate : undefined
          }
          onTimedMetadata={onTimedMetadata ? _onTimedMetadata : undefined}
          onAudioTracks={onAudioTracks ? _onAudioTracks : undefined}
          onTextTracks={onTextTracks ? _onTextTracks : undefined}
          onTextTrackDataChanged={
            onTextTrackDataChanged ? _onTextTrackDataChanged : undefined
          }
          onVideoTracks={onVideoTracks ? _onVideoTracks : undefined}
          onVideoFullscreenPlayerDidDismiss={onFullscreenPlayerDidDismiss}
          onVideoFullscreenPlayerDidPresent={onFullscreenPlayerDidPresent}
          onVideoFullscreenPlayerWillDismiss={onFullscreenPlayerWillDismiss}
          onVideoFullscreenPlayerWillPresent={onFullscreenPlayerWillPresent}
          onVideoExternalPlaybackChange={
            onExternalPlaybackChange ? onVideoExternalPlaybackChange : undefined
          }
          onVideoIdle={onIdle}
          onAudioFocusChanged={
            onAudioFocusChanged ? _onAudioFocusChanged : undefined
          }
          onReadyForDisplay={
            onReadyForDisplay || hasPoster ? _onReadyForDisplay : undefined
          }
          onPlaybackRateChange={
            onPlaybackRateChange ? _onPlaybackRateChange : undefined
          }
          onVolumeChange={onVolumeChange ? _onVolumeChange : undefined}
          onVideoAudioBecomingNoisy={onAudioBecomingNoisy}
          onPictureInPictureStatusChanged={
            onPictureInPictureStatusChanged
              ? _onPictureInPictureStatusChanged
              : undefined
          }
          onRestoreUserInterfaceForPictureInPictureStop={
            onRestoreUserInterfaceForPictureInPictureStop
          }
          onVideoAspectRatio={onAspectRatio ? _onVideoAspectRatio : undefined}
          onReceiveAdEvent={
            onReceiveAdEvent
              ? (_onReceiveAdEvent as (e: NativeSyntheticEvent<object>) => void)
              : undefined
          }
          onControlsVisibilityChange={
            onControlsVisibilityChange ? _onControlsVisibilityChange : undefined
          }
          viewType={_viewType}
        />
        {_renderPoster()}
      </View>
    );
  },
);

Video.displayName = 'Video';
export default Video;
