/// <reference types="@applicaster/applicaster-types" />
/// <reference types="@applicaster/zapp-react-native-ui-components" />
import React, { useEffect, useMemo } from "react";
import * as R from "ramda";
import { Platform } from "react-native";
import Url from "url";

import { favoritesListener } from "@applicaster/zapp-react-native-bridge/Favorites";
import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
import { useRoute } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
import {
  getInflatedDataSourceUrl,
  getSearchContext,
  useFeedLoader,
  useFeedRefresh,
} from "@applicaster/zapp-react-native-utils/reactHooks";

import { ZappPipesSearchContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
import { useScreenContext } from "@applicaster/zapp-react-native-utils/reactHooks/screen";

import { isVerticalListOrGrid } from "./utils";

type Props = {
  component: ZappUIComponent;
  feedUrl?: string;
  riverId: string;
  getStaticComponentFeed?: GeneralContentScreenProps["getStaticComponentFeed"];
  componentIndex?: number;

  isScreenWrappedInContainer?: boolean;
  isLast?: boolean;
};

const FAVORITES_TYPE = "FAVOURITES";

function getDataSourceUrl({
  component,
  feedUrl,
  screenData,
  entryContext,
  searchContext,
  screenContext,
}): { dataSourceUrl: string | null; type: string | null } {
  if (feedUrl) {
    return { dataSourceUrl: feedUrl, type: null };
  }

  if (component.data) {
    const {
      data: { source, type, mapping },
    } = component;

    if (type === FAVORITES_TYPE) {
      return { dataSourceUrl: source || FAVORITES_TYPE, type };
    }

    if (source) {
      if (type === "APPLICASTER_COLLECTION" && !R.includes("://", source)) {
        return {
          dataSourceUrl: `applicaster://fetchData?type=${type}&collectionId=${source}`,
          type,
        };
      }

      if (type === "APPLICASTER_CATEGORY" && !R.includes("://", source)) {
        return {
          dataSourceUrl: `applicaster://fetchData?type=${type}&categoryId=${source}&platform=${Platform.OS}`,
          type,
        };
      }

      if (mapping) {
        const contexts = {
          entry: entryContext,
          screen: screenContext || screenData,
          search: getSearchContext(searchContext, mapping),
        };

        const dataSourceUrl = getInflatedDataSourceUrl({
          source,
          mapping,
          contexts,
        });

        if (!dataSourceUrl) {
          return { dataSourceUrl: null, type: null };
        }

        return { dataSourceUrl, type };
      }

      return { dataSourceUrl: source, type };
    }
  }

  return { dataSourceUrl: null, type: null };
}

function applyItemLimit(zappPipesData, component) {
  if (!zappPipesData || !zappPipesData.data) {
    return zappPipesData;
  }

  const { data } = zappPipesData;

  const {
    rules: { item_limit },
  } = component;

  if (item_limit && data.entry && data.entry.length) {
    return {
      ...zappPipesData,
      data: {
        ...data,
        entry: R.slice(
          0,
          Math.min(Number(item_limit), R.length(data.entry)),
          data.entry
        ),
      },
    };
  }

  return zappPipesData;
}

function getListenerFromPlugin(
  dataSourceUrl: string,
  plugins: QuickBrickPlugin[]
): AddDataSourceListener | void {
  const url = Url.parse(dataSourceUrl, false);

  const addListener = R.compose(
    R.path(["module", "addDataSourceListener"]),
    R.find(R.propEq("identifier", url.host))
  )(plugins);

  return addListener;
}

const getMethodAndParams = (component) => {
  const method: "get" | "post" = R.pathOr("get", ["data", "method"], component);

  const bodyParams: Record<string, unknown> = R.pathOr(
    {},
    ["data", "bodyParams"],
    component
  );

  return {
    method,
    bodyParams,
  };
};

const generateStaticUrl = (component: ZappUIComponent) => {
  const { id, position } = component || {};

  return `static://component?id=${id}&position=${position}`;
};

const reducer = (state, action) => {
  switch (action.type) {
    case "SET_DATA":
      return { ...state, data: action.payload, loading: false };
    case "SET_ERROR":
      return { ...state, error: action.payload, loading: false };
    default:
      return state;
  }
};

const useGetStaticFeed = (getter, component, index) => {
  const url = generateStaticUrl(component);

  // refactor above 3 states to use reducer

  const [{ loading, data, error }, dispatch] = React.useReducer(reducer, {
    loading: true,
    data: null,
    error: null,
  });

  React.useEffect(() => {
    const getData = async () => {
      try {
        const res = await getter({ index, component });
        dispatch({ type: "SET_DATA", payload: res });
      } catch (err) {
        dispatch({ type: "SET_ERROR", payload: err });
      }
    };

    if (getter) {
      getData();
    }
  }, [getter, component, index]);

  return { url, loading, data, error };
};

export function zappPipesDataConnector(
  Component: React.FC<any> | React.ComponentClass<any>
) {
  return function WrappedWithZappPipesData(props: Props) {
    const { screenData } = useRoute();
    const { plugins } = usePickFromState(["plugins"]);

    const screenContextData = useScreenContext();

    const {
      component,
      feedUrl,
      getStaticComponentFeed,
      componentIndex,
      isScreenWrappedInContainer,
      riverId,
      isLast = false,
    } = props;

    /**
     * TODO: HACK for the tabs screen which needs to use 2 types of context.
     * ui_components rendered above tabs selector need to use entryContext
     * Screens rendered inside tabs selector need to use nested context.
     * This hack works because ui_components are rendered using ComponentsMap which doesn't have riverId prop
     **/
    const shouldUseNestedContext = isScreenWrappedInContainer && riverId;

    const entryContext =
      (shouldUseNestedContext && screenContextData?.nested?.entry
        ? screenContextData?.nested?.entry
        : screenContextData?.entry) || {};

    const screenContext =
      (shouldUseNestedContext && screenContextData?.nested?.screen
        ? screenContextData?.nested?.screen
        : screenContextData?.screen) || {};

    const [searchContext] = ZappPipesSearchContext.useZappPipesContext();

    const { dataSourceUrl, type } = useMemo(
      () =>
        getDataSourceUrl({
          component,
          feedUrl,
          screenData,
          entryContext,
          searchContext,
          screenContext,
        }),
      [
        component.id,
        feedUrl,
        entryContext,
        searchContext,
        screenContext,
        screenData.id,
      ]
    );

    const shouldClearCache = useMemo(
      () => Boolean(component?.rules?.clear_cache_on_reload),
      [component?.id]
    );

    const pipesOptions = useMemo(
      () => ({
        clearCache: type === FAVORITES_TYPE,
        loadLocalFavorites: type === FAVORITES_TYPE,
        silentRefresh: !shouldClearCache,
        ...getMethodAndParams(component),
      }),
      [type, shouldClearCache, component]
    );

    const { reloadData, loadNext, error, loading, url, data } = useFeedLoader({
      feedUrl: dataSourceUrl ?? "",
      pipesOptions,
    });

    const staticFeed = useGetStaticFeed(
      getStaticComponentFeed,
      component,
      componentIndex
    );

    useEffect(() => {
      if (dataSourceUrl?.includes("pipesv2://") && reloadData) {
        const addListener = getListenerFromPlugin(dataSourceUrl, plugins);

        if (addListener) {
          return addListener(reloadData);
        }
      }
    }, [dataSourceUrl, reloadData]);

    useEffect(() => {
      if (type === FAVORITES_TYPE && reloadData) {
        favoritesListener.on("FAVORITES_CHANGED", reloadData);

        return () => {
          favoritesListener.removeHandler("FAVORITES_CHANGED", reloadData);
        };
      }
    }, [type]);

    const getZappPipesData = React.useCallback(
      () =>
        getStaticComponentFeed && !url
          ? staticFeed
          : { error, loading, url, data },
      [staticFeed?.loading, staticFeed?.data, data?.next, loading, data]
    );

    const zappPipesDataProps = useMemo(() => {
      const loadNextData =
        !isLast && isVerticalListOrGrid(component) ? undefined : loadNext;

      return {
        zappPipesData: applyItemLimit(getZappPipesData(), component),
        reloadData,
        loadNextData,
      };
    }, [
      dataSourceUrl,
      data?.next,
      loading,
      reloadData,
      loadNext,
      getZappPipesData,
      data,
      isLast,
      component,
    ]);

    useFeedRefresh({
      reloadData,
      component,
    });

    return <Component {...props} {...zappPipesDataProps} />;
  };
}
