import type React from 'react';
import { createContext, useContext, useReducer } from 'react';

import isEqual from 'lodash/isEqual';
import isNil from 'lodash/isNil';

import {
  CycleMainStepTypes,
  type CycleStep,
  CycleStepIds,
  NavigationActionTypes,
} from 'src/types/performance';

type NavigationState = {
  activeStep: number;
  activeSubstep: number;
  steps: CycleStep[];
  currentStep: CycleStep | null;
  isGeneralStep: boolean;
  isSettingsStep: boolean;
  isRatingStep: boolean;
  isValidationStep: boolean;
  isGoalStep: boolean;
  isDirectionStep: boolean;
  isOtherSettingsStep: boolean;
  isDirectionWeightsStep: boolean;
};

type NavigationAction =
  | { type: NavigationActionTypes.SET_STEPS; payload: CycleStep[] }
  | { type: NavigationActionTypes.NEXT_STEP }
  | { type: NavigationActionTypes.PREV_STEP }
  | { type: NavigationActionTypes.SET_ACTIVE_STEP; payload: number }
  | { type: NavigationActionTypes.SET_ACTIVE_SUBSTEP; payload: number };

type NavigationContextType = NavigationState & {
  setSteps: (steps: CycleStep[]) => void;
  nextStep: () => void;
  previousStep: () => void;
  setActiveStep: (step: number) => void;
  setActiveSubstep: (substep: number) => void;
};

const initialState: NavigationState = {
  activeStep: 0,
  activeSubstep: 0,
  steps: [],
  currentStep: null,
  isGeneralStep: true,
  isSettingsStep: false,
  isRatingStep: false,
  isValidationStep: false,
  isGoalStep: false,
  isDirectionStep: false,
  isOtherSettingsStep: false,
  isDirectionWeightsStep: false,
};

const navigationReducer = (
  state: NavigationState,
  action: NavigationAction,
): NavigationState => {
  switch (action.type) {
    case NavigationActionTypes.SET_STEPS: {
      if (
        state.steps &&
        action.payload &&
        isEqual(state.steps, action.payload)
      ) {
        const newCalculatedCurrentStep =
          action.payload[state.activeStep] || null;
        if (isEqual(state.currentStep, newCalculatedCurrentStep)) {
          return state;
        }
      }
      const setStepsCurrentStep = action.payload[state.activeStep] || null;
      const totalSteps = action.payload.length;
      const isLastStep = state.activeStep === totalSteps - 1;
      const isRatingStepActive =
        state.activeStep === CycleMainStepTypes.RATING_STEP;

      return {
        ...state,
        steps: action.payload,
        currentStep: setStepsCurrentStep,
        isGeneralStep: state.activeStep === CycleMainStepTypes.GENERAL_STEP,
        isSettingsStep: state.activeStep === CycleMainStepTypes.SETTINGS_STEP,
        isRatingStep: isRatingStepActive,
        isValidationStep: isLastStep,
        // Calculate isOtherSettingsStep here because Rating Step only has one substep.
        // Unlike Settings Step which has multiple substeps (Score, Goal, etc.)
        isOtherSettingsStep:
          isRatingStepActive &&
          setStepsCurrentStep?.substeps?.[state.activeSubstep]?.id ===
            CycleStepIds.OTHER_SETTINGS_SUBSTEP_ID,
      };
    }
    case NavigationActionTypes.NEXT_STEP: {
      const currentStep = state.steps[state.activeStep];
      if (
        currentStep?.substeps?.length &&
        currentStep.substeps.length > state.activeSubstep + 1
      ) {
        const newSubstep = state.activeSubstep + 1;
        return {
          ...state,
          activeSubstep: newSubstep,
          isGoalStep:
            state.isSettingsStep &&
            currentStep.substeps[newSubstep]?.id ===
              CycleStepIds.GOAL_SUBSTEP_ID,
          isDirectionStep:
            state.isGeneralStep &&
            currentStep.substeps[newSubstep]?.id ===
              CycleStepIds.DIRECTION_SUBSTEP_ID,
          isOtherSettingsStep:
            state.isRatingStep &&
            currentStep.substeps[newSubstep]?.id ===
              CycleStepIds.OTHER_SETTINGS_SUBSTEP_ID,
          isDirectionWeightsStep:
            state.isRatingStep &&
            currentStep.substeps[newSubstep]?.id ===
              CycleStepIds.DIRECTION_WEIGHTS_SUBSTEP_ID,
        };
      } else {
        const newStep = Math.min(state.activeStep + 1, state.steps.length - 1);
        const newCurrentStep = state.steps[newStep] || null;
        const totalSteps = state.steps.length;
        const isLastStep = newStep === totalSteps - 1;
        const isRatingStepActive = newStep === CycleMainStepTypes.RATING_STEP;

        return {
          ...state,
          activeStep: newStep,
          activeSubstep: 0,
          currentStep: newCurrentStep,
          isGeneralStep: newStep === CycleMainStepTypes.GENERAL_STEP,
          isSettingsStep: newStep === CycleMainStepTypes.SETTINGS_STEP,
          isRatingStep: isRatingStepActive,
          isValidationStep: isLastStep,
          isGoalStep: false,
          isDirectionStep: false,
          isOtherSettingsStep:
            isRatingStepActive &&
            newCurrentStep?.substeps?.[0]?.id ===
              CycleStepIds.OTHER_SETTINGS_SUBSTEP_ID,
          isDirectionWeightsStep:
            newStep === CycleMainStepTypes.RATING_STEP &&
            newCurrentStep?.substeps?.[0]?.id ===
              CycleStepIds.DIRECTION_WEIGHTS_SUBSTEP_ID,
        };
      }
    }
    case NavigationActionTypes.PREV_STEP: {
      const currentStep = state.steps[state.activeStep];
      if (currentStep?.substeps?.length && state.activeSubstep > 0) {
        const newSubstep = state.activeSubstep - 1;
        return {
          ...state,
          activeSubstep: newSubstep,
          isGoalStep:
            state.isSettingsStep &&
            currentStep.substeps[newSubstep]?.id ===
              CycleStepIds.GOAL_SUBSTEP_ID,
          isDirectionStep:
            state.isGeneralStep &&
            currentStep.substeps[newSubstep]?.id ===
              CycleStepIds.DIRECTION_SUBSTEP_ID,
          isOtherSettingsStep:
            state.isRatingStep &&
            currentStep.substeps[newSubstep]?.id ===
              CycleStepIds.OTHER_SETTINGS_SUBSTEP_ID,
          isDirectionWeightsStep:
            state.isRatingStep &&
            currentStep.substeps[newSubstep]?.id ===
              CycleStepIds.DIRECTION_WEIGHTS_SUBSTEP_ID,
        };
      } else {
        if (state.activeStep === 0) {
          return state;
        }

        const newStep = Math.max(state.activeStep - 1, 0);
        const prevStep = state.steps[newStep] || null;
        const lastSubstep = prevStep?.substeps?.length
          ? prevStep.substeps.length - 1
          : 0;
        const totalSteps = state.steps.length;
        const isLastStep = newStep === totalSteps - 1;
        const isRatingStepActive = newStep === CycleMainStepTypes.RATING_STEP;

        return {
          ...state,
          activeStep: newStep,
          activeSubstep: lastSubstep,
          currentStep: prevStep,
          isGeneralStep: newStep === CycleMainStepTypes.GENERAL_STEP,
          isSettingsStep: newStep === CycleMainStepTypes.SETTINGS_STEP,
          isRatingStep: isRatingStepActive,
          isValidationStep: isLastStep,
          isGoalStep:
            newStep === CycleMainStepTypes.SETTINGS_STEP &&
            prevStep?.substeps?.[lastSubstep]?.id ===
              CycleStepIds.GOAL_SUBSTEP_ID,
          isDirectionStep:
            newStep === CycleMainStepTypes.GENERAL_STEP &&
            prevStep?.substeps?.[lastSubstep]?.id ===
              CycleStepIds.DIRECTION_SUBSTEP_ID,
          isOtherSettingsStep:
            isRatingStepActive &&
            prevStep?.substeps?.[lastSubstep]?.id ===
              CycleStepIds.OTHER_SETTINGS_SUBSTEP_ID,
          isDirectionWeightsStep:
            newStep === CycleMainStepTypes.RATING_STEP &&
            prevStep?.substeps?.[lastSubstep]?.id ===
              CycleStepIds.DIRECTION_WEIGHTS_SUBSTEP_ID,
        };
      }
    }
    case NavigationActionTypes.SET_ACTIVE_STEP: {
      const newStep = Math.min(
        Math.max(action.payload, 0),
        state.steps.length - 1,
      );
      const newCurrentStep = state.steps[newStep] || null;
      const totalSteps = state.steps.length;
      const isLastStep = newStep === totalSteps - 1;
      const isRatingStepActive = newStep === CycleMainStepTypes.RATING_STEP;

      return {
        ...state,
        activeStep: newStep,
        activeSubstep: 0,
        currentStep: newCurrentStep,
        isGeneralStep: newStep === CycleMainStepTypes.GENERAL_STEP,
        isSettingsStep: newStep === CycleMainStepTypes.SETTINGS_STEP,
        isRatingStep: isRatingStepActive,
        isValidationStep: isLastStep,
        isGoalStep: false,
        isDirectionStep: false,
        isOtherSettingsStep:
          isRatingStepActive &&
          newCurrentStep?.substeps?.[0]?.id ===
            CycleStepIds.OTHER_SETTINGS_SUBSTEP_ID,
        isDirectionWeightsStep:
          newStep === CycleMainStepTypes.RATING_STEP &&
          newCurrentStep?.substeps?.[0]?.id ===
            CycleStepIds.DIRECTION_WEIGHTS_SUBSTEP_ID,
      };
    }
    case NavigationActionTypes.SET_ACTIVE_SUBSTEP: {
      const currentStep = state.steps[state.activeStep];
      const maxSubstep = currentStep?.substeps?.length
        ? currentStep.substeps.length - 1
        : 0;
      const newSubstep = Math.min(Math.max(action.payload, 0), maxSubstep);

      return {
        ...state,
        activeSubstep: newSubstep,
        isGoalStep:
          state.isSettingsStep &&
          currentStep?.substeps?.[newSubstep]?.id ===
            CycleStepIds.GOAL_SUBSTEP_ID,
        isDirectionStep:
          state.isGeneralStep &&
          currentStep?.substeps?.[newSubstep]?.id ===
            CycleStepIds.DIRECTION_SUBSTEP_ID,
        isOtherSettingsStep:
          state.isRatingStep &&
          currentStep?.substeps?.[newSubstep]?.id ===
            CycleStepIds.OTHER_SETTINGS_SUBSTEP_ID,
        isDirectionWeightsStep:
          state.isRatingStep &&
          currentStep?.substeps?.[newSubstep]?.id ===
            CycleStepIds.DIRECTION_WEIGHTS_SUBSTEP_ID,
      };
    }
    default:
      return state;
  }
};

const NavigationContext = createContext<NavigationContextType | undefined>(
  undefined,
);

export const NavigationProvider = ({
  children,
  initialActiveStep = 0,
  initialActiveSubstep = 0,
}: {
  children: React.ReactNode;
  initialActiveStep?: number;
  initialActiveSubstep?: number;
}) => {
  const [state, dispatch] = useReducer(navigationReducer, {
    ...initialState,
    activeStep: initialActiveStep,
    activeSubstep: initialActiveSubstep,
  });

  const setSteps = (steps: CycleStep[]) => {
    dispatch({ type: NavigationActionTypes.SET_STEPS, payload: steps });
  };

  const nextStep = () => {
    dispatch({ type: NavigationActionTypes.NEXT_STEP });
  };

  const previousStep = () => {
    dispatch({ type: NavigationActionTypes.PREV_STEP });
  };

  const setActiveStep = (step: number) => {
    dispatch({ type: NavigationActionTypes.SET_ACTIVE_STEP, payload: step });
  };

  const setActiveSubstep = (substep: number) => {
    dispatch({
      type: NavigationActionTypes.SET_ACTIVE_SUBSTEP,
      payload: substep,
    });
  };

  const value = {
    ...state,
    setSteps,
    nextStep,
    previousStep,
    setActiveStep,
    setActiveSubstep,
  };

  return (
    <NavigationContext.Provider value={value}>
      {children}
    </NavigationContext.Provider>
  );
};

export const useNavigation = () => {
  const context = useContext(NavigationContext);
  if (isNil(context)) {
    throw new Error('useNavigation must be used within a NavigationProvider');
  }
  return context;
};
