import * as React from "react";
import * as R from "ramda";

const allTrue = R.all(R.equals(true));
const anyFalse = R.any(R.equals(false));
const anyTrue = R.any(R.equals(true));

const reducer = (state, { payload }) => {
  if (!R.isNil(payload) && !state[payload]) {
    return R.set(R.lensIndex(payload), true)(state);
  }

  return state;
};

type Return = {
  isAnyLoading: boolean;
  isAllLoaded: boolean;
  waitForAllComponents: boolean;
  onLoadFinished: (index: number) => void;
  onLoadFailed: ({ error, index }: { error: Error; index: number }) => void;
  shouldShowLoadingError: boolean;
  isAnyLoaded: boolean;
  arePreviousComponentsLoaded: (index: number) => boolean;
};

type Action = { payload: { index: number } };

type Loaded = true;
type Loading = false;

type LoadingState = Array<Loaded | Loading>;

// TODO: Take this value from Zapp configuration, when feature is added to GeneralScreen
const SHOULD_FAIL_ON_COMPONENT_LOADING = false;

export const useLoadingState = (count: number): Return => {
  const [loadingError, setLoadingError] = React.useState(null);

  const [componentsState, dispatch] = React.useReducer<
    React.Reducer<LoadingState, Action>
  >(reducer, new Array(count).fill(false));

  const handleComponentLoaded = React.useCallback((index) => {
    dispatch({ payload: index });
  }, []);

  const handleComponentLoadErrorWhenNeedToFail = React.useCallback(
    ({ error }) => {
      if (error !== loadingError) {
        setLoadingError(error);
      }
    },
    []
  );

  const handleComponentLoadErrorWhenNoNeedToFail = React.useCallback(
    ({ index }) => {
      handleComponentLoaded(index);
    },
    []
  );

  const arePreviousComponentsLoaded = React.useCallback(
    (index) => {
      if (index === 0) {
        return true;
      }

      const componentsBefore = R.slice(0, index, componentsState);

      return allTrue(componentsBefore);
    },
    [componentsState]
  );

  return {
    isAnyLoading: anyFalse(componentsState),
    isAllLoaded: allTrue(componentsState),
    onLoadFinished: handleComponentLoaded,
    onLoadFailed: SHOULD_FAIL_ON_COMPONENT_LOADING
      ? handleComponentLoadErrorWhenNeedToFail
      : handleComponentLoadErrorWhenNoNeedToFail,
    shouldShowLoadingError: SHOULD_FAIL_ON_COMPONENT_LOADING && loadingError,
    isAnyLoaded: anyTrue(componentsState),
    waitForAllComponents: SHOULD_FAIL_ON_COMPONENT_LOADING,
    arePreviousComponentsLoaded,
  };
};
