import * as React from "react";
import { isNil, set, lensIndex, slice } from "ramda";
import { BehaviorSubject } from "rxjs";
import { useRefWithInitialValue } from "@applicaster/zapp-react-native-utils/reactHooks/state/useRefWithInitialValue";

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

  return state;
};

type LoadingState = {
  index: number;
  done: boolean;
  waitForAllComponents: boolean;
};

type Return = {
  loadingState: BehaviorSubject<LoadingState>;
  onLoadFinished: (index: number) => void;
  onLoadFailed: ({ error, index }: { error: Error; index: number }) => void;
  shouldShowLoadingError: boolean;
  arePreviousComponentsLoaded: (index: number) => boolean;
};

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

const createLoadingStateObservable = (count: number) =>
  new BehaviorSubject<LoadingState>({
    index: -1,
    done: count === 0,
    waitForAllComponents: SHOULD_FAIL_ON_COMPONENT_LOADING,
  });

export const useLoadingState = (
  count: number,
  onLoadDone: () => void
): Return => {
  const componentStateRef = React.useRef(new Array(count).fill(false));
  const [loadingError, setLoadingError] = React.useState(null);

  const loadingState = useRefWithInitialValue<BehaviorSubject<LoadingState>>(
    () => createLoadingStateObservable(count)
  );

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

    const componentsBefore = slice(0, index, componentStateRef.current);

    return componentsBefore.every(Boolean);
  }, []);

  const dispatch = React.useCallback(({ payload }) => {
    const newState = reducer(componentStateRef.current, { payload });
    componentStateRef.current = newState;
    const isDone = arePreviousComponentsLoaded(count - 1);

    const state = loadingState.current.getValue();

    const newLoadingState = {
      ...state,
      index: state.index < payload ? payload : state.index,
      done: isDone,
    };

    loadingState.current.next(newLoadingState);

    if (isDone) {
      onLoadDone();
    }
  }, []);

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

  return React.useMemo(
    () => ({
      loadingState: loadingState.current,
      onLoadFinished: handleComponentLoaded,
      onLoadFailed: SHOULD_FAIL_ON_COMPONENT_LOADING
        ? handleComponentLoadErrorWhenNeedToFail
        : handleComponentLoadErrorWhenNoNeedToFail,
      shouldShowLoadingError: SHOULD_FAIL_ON_COMPONENT_LOADING && loadingError,
      arePreviousComponentsLoaded,
    }),
    [
      loadingError,
      handleComponentLoaded,
      handleComponentLoadErrorWhenNeedToFail,
      handleComponentLoadErrorWhenNoNeedToFail,
      arePreviousComponentsLoaded,
    ]
  );
};
