import { useOnboardingDataProvider } from './DataProvider';
import { OnboardingModes } from '@/components/Onboarding/enums';
import { ITipDescriptor } from '@/components/Onboarding/types';
import _ from 'lodash';
import { PropsWithChildren, createContext, useEffect, useMemo, useReducer } from 'react';

enum OnboardingActionType {
  RESTART = 'RESTART',
  SET_SHOW = 'SET_SHOW',
  REGISTER_TIP = 'REGISTER_TIP',
  UNREGISTER_TIP = 'UNREGISTER_TIP',
  DISMISS_TIP = 'DISMISS_TIP',
  SET_ON_COMPLETE = 'SET_ON_COMPLETE'
}

type IOnboardingProviderProps = PropsWithChildren<{
  mode: OnboardingModes;
  topic?: string;
}>;
type IOnCompleteCallback = () => void;
type IOnboardingState = {
  show: boolean;
  tips: Array<ITipDescriptor>;
  currentStep: number;
  onComplete: IOnCompleteCallback;
};
type IOnboardingAction =
  | { type: OnboardingActionType.RESTART }
  | { type: OnboardingActionType.SET_SHOW; payload: boolean }
  | { type: OnboardingActionType.REGISTER_TIP; payload: ITipDescriptor }
  | { type: OnboardingActionType.UNREGISTER_TIP; payload: ITipDescriptor | string }
  | { type: OnboardingActionType.DISMISS_TIP; payload: ITipDescriptor | string }
  | { type: OnboardingActionType.SET_ON_COMPLETE; payload: IOnCompleteCallback };
type IOnboardingContext = { state: IOnboardingState; dispatch: (action: IOnboardingAction) => void };

function reducer(state: IOnboardingState, action: IOnboardingAction): IOnboardingState {
  const newState = _.clone(state);
  const { type } = action;

  switch (type) {
    case OnboardingActionType.SET_SHOW: {
      const { payload } = action;
      return _.extend(newState, { show: payload });
    }
    case OnboardingActionType.REGISTER_TIP: {
      const { payload } = action;
      const { id } = payload;
      const tips = _.chain(newState.tips)
        .clone()
        .reject((t) => t.id === id)
        .value();
      const existingTip = _.find(newState.tips, { id: id });
      const newTip = _.chain(existingTip ?? {})
        .clone()
        .extend(payload)
        .value();

      tips.push(newTip);
      return _.extend(newState, { tips: _.sortBy(tips, ['step']) });
    }
    case OnboardingActionType.UNREGISTER_TIP: {
      const { payload } = action;
      const id = _.isString(payload) ? payload : payload.id;
      const tips = _.chain(newState.tips)
        .reject((t) => t.id === id)
        .sortBy(['step'])
        .value();
      return _.extend(newState, { tips: tips });
    }
    case OnboardingActionType.DISMISS_TIP: {
      const { payload } = action;
      const id = _.isString(payload) ? payload : payload.id;
      const tips = _.chain(newState.tips)
        .reject((t) => t.id === id)
        .value();
      const tip = _.find(newState.tips, { id: id });

      if (_.isNil(tip)) {
        return newState;
      }

      const newTip = _.chain(tip).clone().extend({ dismissed: true }).value();
      tips.push(newTip);
      const updatedStep =
        _.chain(tips)
          .reject((t) => t.dismissed ?? false)
          .minBy('step')
          .value()?.step ?? 0;
      const isOnboardingComplete =
        newTip?.completeOnboarding &&
        _.chain(tips)
          .reject((t) => t.step < updatedStep)
          .reject((t) => t.dismissed ?? false)
          .isEmpty()
          .value();

      if (isOnboardingComplete) {
        state.onComplete();
      }

      return _.extend(newState, { tips: _.sortBy(tips, ['step']), currentStep: updatedStep });
    }
    case OnboardingActionType.SET_ON_COMPLETE: {
      const { payload } = action;
      return _.extend(newState, { onComplete: payload });
    }
    case OnboardingActionType.RESTART: {
      const tips = _.chain(newState.tips)
        .clone()
        .map((t) => _.omit(t, 'dismissed'))
        .value();
      return _.extend(newState, { tips: tips, currentStep: 0 });
    }
    default:
      return newState;
  }
}

const OnboardingContext = createContext<IOnboardingContext | undefined>(undefined);

function OnboardingProvider(props: IOnboardingProviderProps) {
  const { mode, topic = 'default' } = props;
  const { onboardingRequired, acknowledgeOnboarding } = useOnboardingDataProvider(mode, topic);
  const [state, dispatch] = useReducer(reducer, {
    show: onboardingRequired,
    tips: [],
    currentStep: 0,
    onComplete: () => {}
  });
  const value = useMemo(() => ({ state, dispatch }), [state, dispatch]);

  useEffect(() => {
    dispatch({ type: OnboardingActionType.SET_SHOW, payload: onboardingRequired });
  }, [onboardingRequired, dispatch]);

  useEffect(() => {
    function onComplete() {
      acknowledgeOnboarding().then(() => {
        dispatch({ type: OnboardingActionType.SET_SHOW, payload: false });
      });
    }
    dispatch({ type: OnboardingActionType.SET_ON_COMPLETE, payload: onComplete });
  }, [acknowledgeOnboarding, dispatch]);

  return <OnboardingContext.Provider value={value}>{props.children}</OnboardingContext.Provider>;
}

export type { IOnboardingAction, IOnboardingContext, IOnboardingState };

export { OnboardingActionType, OnboardingContext, OnboardingProvider };
