import { isNil, complement, compose, map, min, prop, take, uniq } from "ramda";
import { useDispatch } from "react-redux";
import * as React from "react";
import { useZappPipesFeeds } from "@applicaster/zapp-react-native-redux/hooks/useZappPipesFeeds";
import { loadPipesData } from "@applicaster/zapp-react-native-redux/ZappPipes";
import { isNilOrEmpty } from "../../reactUtils/helpers";
import { ZappPipesSearchContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
import {
  getInflatedDataSourceUrl,
  getSearchContext,
} from "@applicaster/zapp-react-native-utils/reactHooks";
import { useScreenContext } from "../screen/useScreenContext";

type Options = {
  initialBatchSize?: number;
  riverId?: string;
  isNestedScreen?: boolean;
};

const DEFAULT_BATCH_SIZE = 5;

const dataOf: (feed: ZappPipesData) => ZappPipesData["data"] = prop("data");

const feedIsLoading: (feed: ZappPipesData) => ZappPipesData["loading"] =
  prop("loading");

const getErrorProp: (feed: ZappPipesData) => ZappPipesData["error"] =
  prop("error");

const feedHasData: (feed: ZappPipesData) => boolean = compose(
  complement(isNil),
  dataOf
);

type ZappDataSourceSource = ZappDataSource["source"];

type ZappPipesCollection = Record<string, ZappPipesData>;

const checkFeedIsReady = (feed: ZappPipesData): boolean =>
  feed && !feedIsLoading(feed) && (feedHasData(feed) || !!getErrorProp(feed));

const filterEmptyData = (data) => {
  if (isNilOrEmpty(data) || isNilOrEmpty(data?.source)) return false;

  return true;
};

const getData = (rawData) =>
  rawData.component_type === "gallery-qb"
    ? rawData.ui_components[0].data
    : rawData.data;

const extractData = compose(uniq, map(getData));

export const allFeedsIsReady = (
  feeds: ZappPipesCollection,
  urls: (ZappDataSourceSource | null)[]
): boolean =>
  urls.every((url) =>
    isNil(url) ? true : url && feeds[url] ? checkFeedIsReady(feeds[url]) : false
  );

export const useBatchLoading = (
  componentsToRender: { data?: ZappDataSource; component_type: string }[],
  options: Options
) => {
  const dispatch = useDispatch();
  const { screen: screenContext, entry: entryContext } = useScreenContext();
  const [searchContext] = ZappPipesSearchContext.useZappPipesContext();
  const [hasEverBeenReady, setHasEverBeenReady] = React.useState(false);

  // if first component is gallery-qb, take only one component for initial load
  const takeSize =
    componentsToRender?.[0]?.component_type === "gallery-qb"
      ? 1
      : min(
          options.initialBatchSize ?? DEFAULT_BATCH_SIZE,
          componentsToRender.length
        );

  const takeBatchSize = React.useCallback(take(takeSize), [takeSize]);

  // Prepare inflated url for datasource
  const getUrl = React.useCallback(
    (data) => {
      const mappedFeed = data.mapping
        ? getInflatedDataSourceUrl({
            source: data.source,
            contexts: {
              entry: entryContext,
              screen: screenContext,
              search: getSearchContext(searchContext, data.mapping),
            },
            mapping: data.mapping,
          })
        : data.source;

      return mappedFeed;
    },
    [entryContext, screenContext, searchContext]
  );

  // components for initial load
  const batchComponents = takeBatchSize(componentsToRender);

  // contains datasources from batch components loading
  const feedData: ZappDataSource[] = React.useMemo(
    () => extractData(batchComponents).filter(filterEmptyData),
    [batchComponents]
  );

  // contains inflated urls from batch components loading
  const feedUrls: ZappDataSourceSource[] = React.useMemo(
    () =>
      feedData.map((data) => {
        const mappedFeedUrl = getUrl(data);

        return mappedFeedUrl;
      }),
    []
  );

  const feeds = useZappPipesFeeds(feedUrls);

  // dispatch loadPipesData for each feed that is not loaded
  const runBatchLoading = React.useCallback(() => {
    batchComponents.forEach((rawData: any) => {
      // 1. get data (dataOf)

      const data = getData(rawData);

      // 2. filter out empty data filterEmptyData
      if (!filterEmptyData(data)) return false;

      // 3. filter out data that is already loaded (feeds[data.source]) &  !feedHasData(feed) && !feedIsLoading(feed);
      const feed = feeds[data?.source];

      if (!feed || (!feedHasData(feed) && !feedIsLoading(feed))) {
        const mappedFeedUrl = getUrl(data);

        if (mappedFeedUrl) {
          // 4. load data
          return dispatch(
            loadPipesData(mappedFeedUrl, { riverId: options.riverId })
          );
        }
      }
    });
  }, [feedUrls]);

  React.useEffect(() => {
    runBatchLoading();
  }, []);

  React.useEffect(() => {
    // check if all feeds are ready and set hasEverBeenReady to true
    if (allFeedsIsReady(feeds, feedUrls) && !hasEverBeenReady) {
      setHasEverBeenReady(true);
    }
  }, [feeds, feedUrls, hasEverBeenReady]);

  return hasEverBeenReady;
};
