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

import { Placeholder } from "./Placeholder";
import { useInitialLoading } from "./hooks";
import { useFocusable } from "../Focusable/index.android";

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 { setIsFocusable } = useFocusable();

    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]
    );

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

    React.useEffect(() => {
      const focusable = ifEmptyUseFallback(
        props.component?.rules?.component_cells_focusable,
        true
      );

      // Android TV only fix for focusable components - manually set focusable false
      if (!focusable && isAndroidTVPlatform()) {
        setIsFocusable(focusable);
      }
    }, [props.component?.rules?.component_cells_focusable]);

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

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

    if (zappPipesData?.error) {
      onLoadFailed?.({
        error: zappPipesData.error,
        index: componentIndex,
      });

      return RenderComponent(ErrorComponent, componentProps);
    }

    return RenderComponent(Component, componentProps);
  };
}
