import * as React from "react";
import * as R from "ramda";
import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
import {
  UIComponentContext,
  useUIComponentContext,
} from "@applicaster/zapp-react-native-ui-components/Contexts/UIComponentContext";

import { Placeholder } from "./Placeholder";
import { useInitialLoading } from "./hooks";

type ReactComponent = React.ComponentType<any>;

type Options = {
  skipOnLoadFinished?: boolean;
  allowsEmptyDataSource?: boolean;
};

type ZappUIComponentType = {
  Component: ReactComponent;
  ErrorComponent?: ReactComponent;
  LoadingComponent?: ReactComponent;
  options?: Options;
};

type RenderComponentProps = any;

const RenderComponent = (
  Component: ReactComponent | undefined,
  args: RenderComponentProps
) => {
  const { component, parent } = args;

  const contextValue = React.useMemo(
    () => ({ ...component, parent }),
    [component, parent]
  );

  if (Component) {
    return (
      <UIComponentContext.Provider value={contextValue}>
        <Component {...args} />
      </UIComponentContext.Provider>
    );
  }

  return null;
};

const isDataSourceEmpty = R.compose(
  R.isEmpty,
  R.pathOr({}, ["component", "data", "source"])
);

const isZappUIComponentType = (
  component: ReactComponent | ZappUIComponentType
): component is ZappUIComponentType => {
  return !R.isNil(R.path(["Component"], component));
};

const extract = (
  ComponentDeprecatedOrProps: ReactComponent | ZappUIComponentType,
  ErrorComponentDeprecated?: ReactComponent,
  LoadingComponentDeprecated?: ReactComponent,
  optionsDeprecated?: Options
) => {
  if (isZappUIComponentType(ComponentDeprecatedOrProps)) {
    return {
      Component: ComponentDeprecatedOrProps.Component,
      ErrorComponent: ComponentDeprecatedOrProps.ErrorComponent,
      LoadingComponent: ComponentDeprecatedOrProps.LoadingComponent,
      options: ComponentDeprecatedOrProps.options,
    };
  }

  return {
    Component: ComponentDeprecatedOrProps,
    ErrorComponent: ErrorComponentDeprecated,
    LoadingComponent: LoadingComponentDeprecated,
    options: optionsDeprecated,
  };
};

/**
 * This Decorator is used to provide built-in standard behaviour for Zapp UI Components
 * It will automatically render the proper component depending on the status of
 * the zapp pipes request.
 * @param {Object} props
 * @param {Function} props.Component: React component to be decorated,
 * @param {Function} props.ErrorComponent: optional component to render when zapp pipes returns an error
 * @param {Function} props.LoadingComponent: optional component to render when zapp pipes data is loading
 * @param {Object} props.options
 * @param {Boolean} props.options.skipOnLoadFinished: skips invocation of onLoadFinished
 * @returns {Function} wrapped component
 */
export function ZappUIComponent(
  ComponentDeprecatedOrProps: ReactComponent | ZappUIComponentType,
  ErrorComponentDeprecated?: ReactComponent,
  LoadingComponentDeprecated?: ReactComponent,
  optionsDeprecated?: Options
) {
  const usingDeprecatedSignature = !isZappUIComponentType(
    ComponentDeprecatedOrProps
  );

  if (usingDeprecatedSignature && __DEV__) {
    // eslint-disable-next-line no-console
    console.warn(
      `Deprecation Warning!
ZappUIComponent call signature has changed from Quaternary to Unary object literal.
New signature: {Component, ErrorComponent, LoadingComponent, options}`
    );
  }

  const { Component, ErrorComponent, LoadingComponent, options } = extract(
    ComponentDeprecatedOrProps,
    ErrorComponentDeprecated,
    LoadingComponentDeprecated,
    optionsDeprecated
  );

  return function WrappedWithZappComponent(props: ZappUIComponentProps) {
    const { zappPipesData, onLoadFinished, onLoadFailed, componentIndex } =
      props;

    const parent = useUIComponentContext();

    const isLoading = toBooleanWithDefaultFalse(zappPipesData?.loading);
    const isInitialLoading = useInitialLoading(isLoading);

    const skipOnLoadFinished = toBooleanWithDefaultFalse(
      options?.skipOnLoadFinished
    );

    const allowsEmptyDataSource = toBooleanWithDefaultFalse(
      options?.allowsEmptyDataSource
    );

    const componentProps = React.useMemo(
      () => ({
        ...props,
        parent,
      }),
      [props, parent]
    );

    const hasError = !!zappPipesData?.error;

    React.useEffect(() => {
      if (!skipOnLoadFinished && !isLoading) {
        onLoadFinished();
      }
    }, [isLoading]);

    React.useEffect(() => {
      if (hasError) {
        onLoadFailed?.({
          error: zappPipesData.error,
          index: componentIndex,
        });
      }
    }, [hasError, componentIndex, onLoadFailed, zappPipesData?.error]);

    if (isDataSourceEmpty(props) && allowsEmptyDataSource) {
      return RenderComponent(Component, componentProps);
    }

    if (isLoading && !zappPipesData?.data && isInitialLoading) {
      return RenderComponent(LoadingComponent || Placeholder, componentProps);
    }

    if (hasError) {
      return RenderComponent(ErrorComponent, componentProps);
    }

    return RenderComponent(Component, componentProps);
  };
}
