import React, {
  forwardRef,
  useCallback,
  useImperativeHandle,
  useRef,
} from 'react';
import {
  Image,
  View,
  Text,
  Platform,
  ImageSourcePropType,
  HostComponent,
} from 'react-native';
import invariant from 'invariant';

import RNCWebView, { Commands, NativeProps } from './RNCWebViewNativeComponent';
import RNCWebViewModule from './NativeRNCWebViewModule';

import {
  defaultOriginWhitelist,
  defaultDeeplinkWhitelist,
  defaultRenderError,
  defaultRenderLoading,
  useWebViewLogic,
  versionPasses,
} from './WebViewShared';
import {
  IOSWebViewProps,
  DecelerationRateConstant,
  WebViewSourceUri,
} from './WebViewTypes';

import validateProps from './validation';
import styles from './WebView.styles';

const { resolveAssetSource } = Image;
const processDecelerationRate = (
  decelerationRate: DecelerationRateConstant | number | undefined
) => {
  let newDecelerationRate = decelerationRate;
  if (newDecelerationRate === 'normal') {
    newDecelerationRate = 0.998;
  } else if (newDecelerationRate === 'fast') {
    newDecelerationRate = 0.99;
  }
  return newDecelerationRate;
};

// Exodus: Hardcoded minimum iOS versions for security
// Format: "minVersion <maxVersion, ..." means minVersion <= iOS < maxVersion
// Last entry has no upper bound
const hardMinimumIOSVersion = '12.5.6 <13, 13.6.1 <14, 14.8.1 <15, 15.7.1';

/**
 * Exodus: Hardcoded security defaults that cannot be overridden by props.
 * These values are always enforced regardless of what the consumer passes.
 */
const securityMediaPlaybackRequiresUserAction = true;
const securityAllowsInlineMediaPlayback = true;
const securityUseSharedProcessPool = false;
const securitySharedCookiesEnabled = false;
const securityEnableApplePay = false;
const securityDataDetectorTypes = ['none'] as const;

const useWarnIfChanges = <T extends unknown>(value: T, name: string) => {
  const ref = useRef(value);
  if (ref.current !== value) {
    console.warn(
      `Changes to property ${name} do nothing after the initial render.`
    );
    ref.current = value;
  }
};

const WebViewComponent = forwardRef<{}, IOSWebViewProps>((props, ref) => {
  // Exodus: Validate props at runtime
  validateProps(props);

  const {
    fraudulentWebsiteWarningEnabled = true,
    javaScriptEnabled = true,
    cacheEnabled = true,
    originWhitelist = defaultOriginWhitelist,
    deeplinkWhitelist = defaultDeeplinkWhitelist,
    textInteractionEnabled = true,
    injectedJavaScript,
    injectedJavaScriptBeforeContentLoaded,
    injectedJavaScriptObject,
    startInLoadingState,
    onNavigationStateChange,
    onLoadStart,
    onError,
    onLoad,
    onLoadEnd,
    onLoadProgress,
    onContentProcessDidTerminate: onContentProcessDidTerminateProp,
    onMessage: onMessageProp,
    onOpenWindow: onOpenWindowProp,
    renderLoading,
    renderError,
    style,
    containerStyle,
    source,
    allowsPictureInPictureMediaPlayback = true,
    incognito,
    decelerationRate: decelerationRateProp,
    onShouldStartLoadWithRequest: onShouldStartLoadWithRequestProp,
    validateMeta,
    validateData,
    minimumIOSVersion,
    unsupportedVersionComponent: UnsupportedVersionComponent,
    ...otherProps
  } = props;

  const webViewRef = useRef<React.ComponentRef<
    HostComponent<NativeProps>
  > | null>(null);

  const onShouldStartLoadWithRequestCallback = useCallback(
    (shouldStart: boolean, _url: string, lockIdentifier = 0) => {
      RNCWebViewModule.shouldStartLoadWithLockIdentifier(
        shouldStart,
        lockIdentifier
      );
    },
    []
  );

  const {
    onLoadingStart,
    onShouldStartLoadWithRequest,
    onMessage,
    viewState,
    setViewState,
    lastErrorEvent,
    onLoadingError,
    onLoadingFinish,
    onLoadingProgress,
    onOpenWindow,
    onContentProcessDidTerminate,
  } = useWebViewLogic({
    onNavigationStateChange,
    onLoad,
    onError,
    onLoadEnd,
    onLoadProgress,
    onLoadStart,
    onMessageProp,
    onOpenWindowProp,
    startInLoadingState,
    originWhitelist,
    deeplinkWhitelist,
    onShouldStartLoadWithRequestProp,
    onShouldStartLoadWithRequestCallback,
    onContentProcessDidTerminateProp,
    validateMeta,
    validateData,
  });

  useImperativeHandle(
    ref,
    () => ({
      goForward: () =>
        webViewRef.current && Commands.goForward(webViewRef.current),
      goBack: () => webViewRef.current && Commands.goBack(webViewRef.current),
      reload: () => {
        setViewState('LOADING');
        if (webViewRef.current) {
          Commands.reload(webViewRef.current);
        }
      },
      stopLoading: () =>
        webViewRef.current && Commands.stopLoading(webViewRef.current),
      postMessage: (data: string) =>
        webViewRef.current && Commands.postMessage(webViewRef.current, data),
      requestFocus: () =>
        webViewRef.current && Commands.requestFocus(webViewRef.current),
      clearCache: (includeDiskFiles: boolean) =>
        webViewRef.current &&
        Commands.clearCache(webViewRef.current, includeDiskFiles),
    }),
    [setViewState, webViewRef]
  );

  useWarnIfChanges(
    securityAllowsInlineMediaPlayback,
    'allowsInlineMediaPlayback'
  );
  useWarnIfChanges(
    allowsPictureInPictureMediaPlayback,
    'allowsPictureInPictureMediaPlayback'
  );
  useWarnIfChanges(incognito, 'incognito');
  useWarnIfChanges(
    securityMediaPlaybackRequiresUserAction,
    'mediaPlaybackRequiresUserAction'
  );
  useWarnIfChanges(securityDataDetectorTypes, 'dataDetectorTypes');

  // Exodus: Check iOS version against minimum requirements
  const iosVersion = String(Platform.Version);
  const passesMinimum = minimumIOSVersion
    ? versionPasses(iosVersion, minimumIOSVersion)
    : true;
  const passesHardMinimum = versionPasses(iosVersion, hardMinimumIOSVersion);

  if (!passesMinimum || !passesHardMinimum) {
    if (UnsupportedVersionComponent) {
      return <UnsupportedVersionComponent />;
    }
    return (
      <View style={{ alignSelf: 'flex-start' }}>
        <Text style={{ color: 'red' }}>
          iOS version is outdated and insecure. Update it to continue.
        </Text>
      </View>
    );
  }

  let otherView = null;
  if (viewState === 'LOADING') {
    otherView = (renderLoading || defaultRenderLoading)();
  } else if (viewState === 'ERROR') {
    invariant(lastErrorEvent != null, 'lastErrorEvent expected to be non-null');
    otherView = (renderError || defaultRenderError)(
      lastErrorEvent?.domain,
      lastErrorEvent?.code ?? 0,
      lastErrorEvent?.description ?? ''
    );
  } else if (viewState !== 'IDLE') {
    console.error(`RNCWebView invalid state encountered: ${viewState}`);
  }

  const webViewStyles = [styles.container, styles.webView, style];
  const webViewContainerStyle = [styles.container, containerStyle];

  const decelerationRate = processDecelerationRate(decelerationRateProp);

  const sourceResolved = resolveAssetSource(source as ImageSourcePropType);
  const newSource =
    typeof sourceResolved === 'object'
      ? Object.entries(sourceResolved as WebViewSourceUri).reduce(
          (prev, [currKey, currValue]) => {
            return {
              ...prev,
              [currKey]:
                currKey === 'headers' &&
                currValue &&
                typeof currValue === 'object'
                  ? Object.entries(currValue).map(([key, value]) => {
                      return {
                        name: key,
                        value,
                      };
                    })
                  : currValue,
            };
          },
          {}
        )
      : sourceResolved;

  const webView = (
    <RNCWebView
      key="webViewKey"
      {...otherProps}
      fraudulentWebsiteWarningEnabled={fraudulentWebsiteWarningEnabled}
      javaScriptEnabled={javaScriptEnabled}
      cacheEnabled={cacheEnabled}
      useSharedProcessPool={securityUseSharedProcessPool}
      sharedCookiesEnabled={securitySharedCookiesEnabled}
      enableApplePay={securityEnableApplePay}
      textInteractionEnabled={textInteractionEnabled}
      decelerationRate={decelerationRate}
      messagingEnabled={typeof onMessageProp === 'function'}
      messagingModuleName="" // android ONLY
      onLoadingError={onLoadingError}
      onLoadingFinish={onLoadingFinish}
      onLoadingProgress={onLoadingProgress}
      onLoadingStart={onLoadingStart}
      onMessage={onMessage}
      onOpenWindow={onOpenWindowProp && onOpenWindow}
      hasOnOpenWindowEvent={onOpenWindowProp !== undefined}
      onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
      onContentProcessDidTerminate={onContentProcessDidTerminate}
      injectedJavaScript={injectedJavaScript}
      injectedJavaScriptBeforeContentLoaded={
        injectedJavaScriptBeforeContentLoaded
      }
      injectedJavaScriptObject={JSON.stringify(injectedJavaScriptObject)}
      dataDetectorTypes={securityDataDetectorTypes}
      allowsInlineMediaPlayback={securityAllowsInlineMediaPlayback}
      allowsPictureInPictureMediaPlayback={allowsPictureInPictureMediaPlayback}
      incognito={incognito}
      mediaPlaybackRequiresUserAction={securityMediaPlaybackRequiresUserAction}
      newSource={newSource}
      style={webViewStyles}
      ref={webViewRef}
      // @ts-expect-error source prop for old arch compatibility
      source={sourceResolved}
    />
  );

  return (
    <View style={webViewContainerStyle}>
      {webView}
      {otherView}
    </View>
  );
});

// no native implementation for iOS, depends only on permissions
const isFileUploadSupported: () => Promise<boolean> = async () => true;

const WebView = Object.assign(WebViewComponent, { isFileUploadSupported });

export default WebView;
