import React, { cloneElement, FC, useEffect, useRef, useState } from 'react';
import { DeviceEventEmitter, View } from 'react-native';
import Logger from '../core/logging/logger';
import CSQMethodFilter from '../csq/CSQMethodFilter';
import { CSWebViewProps } from '../types/types';
import { hasOnlyOneChild, mergeCallbacks } from '../utils/utils';
import { injectWebView, removeWebViewInjection } from './webView';

/**
 * CSWebView is a wrapper around the React Native WebView component.
 *
 * @param children - The child webview component to render.
 */
export const CSWebView: FC<CSWebViewProps> = ({ children }) => {
  const nativeTag = useRef<number | null | undefined>(undefined);
  const injectedWebViewTag = useRef<number | null | undefined>(undefined);
  const reactNativeWebviewSource = React.isValidElement(children)
    ? (children as React.ReactElement<any>).props.source
    : null;
  const hasOneChild = hasOnlyOneChild(children);

  const initialSource = {};

  const [currentSource, setCurrentSource] = useState(initialSource);

  const onTagInjected = () => {
    injectedWebViewTag.current = nativeTag.current;
    setCurrentSource(reactNativeWebviewSource);
  };

  useEffect(() => {
    CSQMethodFilter.warnIfDisabled('default', 'CSWebView');

    return () => {
      if (injectedWebViewTag.current != null) {
        removeWebViewInjection(injectedWebViewTag.current);
      }
    };
  }, []);

  useEffect(() => {
    if (children && !hasOneChild) {
      Logger.error(
        'CSWebView component expects exactly 1 child, but received multiple. Ensure that only a single child element is passed'
      );
    }
  }, [children]);

  useEffect(() => {
    const onCSWebViewInjectedListener = DeviceEventEmitter.addListener(
      'onCSWebViewInjected',
      () => {
        onTagInjected();
      }
    );

    return () => {
      if (onCSWebViewInjectedListener) {
        onCSWebViewInjectedListener.remove();
      }
    };
  }, [reactNativeWebviewSource]);

  useEffect(() => {
    if (injectedWebViewTag.current) {
      // Update url only after the webView has been injected
      setCurrentSource(reactNativeWebviewSource);
    }
  }, [reactNativeWebviewSource]);

  // We need to clone React Native WebView so we can control when the
  // source is updated. We clone it and set the source and onLayout prop accordingly
  const renderChildWebView = () => {
    if (children && hasOneChild) {
      const userOnLayout = (children as React.ReactElement<any>).props.onLayout;
      const originalApplicationNameForUserAgent = (children as React.ReactElement<any>).props.applicationNameForUserAgent;

      const webViewApplicationNameForUserAgent = originalApplicationNameForUserAgent
        ? `${originalApplicationNameForUserAgent} CS_WebView`
        : `CS_WebView`;

      // We need to be sure to call our internal onLayout method which is the responsible of starting
      // the injection process but also we need to keep React Native WebView onLayout prop that could
      // have been defined by the user
      const mergedOnLayout = mergeCallbacks(onLayout, userOnLayout);

      return cloneElement(children, {
        ...(children as React.ReactElement<any>).props,
        onLayout: mergedOnLayout,
        source: currentSource,
        applicationNameForUserAgent: webViewApplicationNameForUserAgent,
      });
    }
  };

  const onLayout = (event: any) => {
    nativeTag.current = event.nativeEvent.target;
    if (nativeTag.current && nativeTag.current !== injectedWebViewTag.current) {
      injectWebView(nativeTag.current);
    }
  };

  return <View style={{ flex: 1 }}>{renderChildWebView()}</View>;
};
