import { IItem } from '@/components/ra-forms/LongForm/types';
import { getChildren, getItemsIds, getLevel } from '@/components/ra-forms/LongForm/utils';
import _ from 'lodash';
import { createContext, useCallback, useContext, useEffect, useMemo, useReducer } from 'react';
import { matchPath, useLocation } from 'react-router';

enum ActionType {
  SET_FORM_ROOT_PATH = 'setFormRootPath',
  SET_SYNC_WITH_LOCATION = 'setSyncWithLocation',
  SET_ACTIVE_ITEM = 'setActiveItem',
  ADD_ITEM = 'addItem',
  REMOVE_ITEM = 'removeItem',
  SET_ERROR_COUNT = 'setErrorCount'
}

type IProviderProps = React.PropsWithChildren<{
  syncWithLocation?: boolean;
  rootMatchString?: string;
  errorCount?: boolean;
}>;
type IState = {
  formRootPath?: string;
  syncWithLocation: boolean;
  errorCount: boolean;
  items: Array<IItem>;
  activeItem?: string;
};
type IAction =
  | {
      type: ActionType.SET_FORM_ROOT_PATH;
      payload: string;
    }
  | {
      type: ActionType.SET_SYNC_WITH_LOCATION;
      payload: boolean;
    }
  | {
      type: ActionType.SET_ERROR_COUNT;
      payload: boolean;
    }
  | {
      type: ActionType.SET_ACTIVE_ITEM;
      payload?: string;
    }
  | {
      type: ActionType.ADD_ITEM;
      payload: IItem;
    }
  | {
      type: ActionType.REMOVE_ITEM;
      payload: string | IItem;
    };
type IDispatch = React.Dispatch<IAction>;
type IContext = {
  state: IState;
  dispatch: IDispatch;
};

function reducer(state: IState, action: IAction): IState {
  const newState = _.clone(state);
  const { type, payload } = action;
  switch (type) {
    case ActionType.SET_FORM_ROOT_PATH:
      return _.extend(newState, { formRootPath: payload });
    case ActionType.SET_SYNC_WITH_LOCATION:
      return _.extend(newState, { syncWithLocation: payload });
    case ActionType.SET_ERROR_COUNT:
      return _.extend(newState, { errorCount: payload });
    case ActionType.SET_ACTIVE_ITEM:
      return _.includes(getItemsIds(state.items), payload) ? _.extend(newState, { activeItem: payload }) : newState;
    case ActionType.ADD_ITEM: {
      const { id } = payload;
      return _.extend(newState, {
        items: _.chain(newState.items)
          .clone()
          .reject((item: IItem) => item.id === id)
          .concat([payload])
          .orderBy((item) => item.index)
          .value()
      });
    }
    case ActionType.REMOVE_ITEM: {
      const id = _.isString(payload) ? payload : payload.id;
      const items = _.chain(newState.items)
        .clone()
        .reject((item: IItem) => item.id === id)
        .value();
      _.extend(newState, { items: items });
      if (newState.activeItem === id) {
        _.extend(newState, { activeItem: _.first(items)?.id ?? undefined });
      }

      return newState;
    }
    default:
      return newState;
  }
}

const DefaultState: IState = {
  syncWithLocation: true,
  errorCount: true,
  items: [],
  activeItem: undefined
};

const Context = createContext<IContext | undefined>(undefined);

function Provider(props: IProviderProps) {
  const syncWithLocation = Boolean(props.syncWithLocation ?? true);
  const errorCount = Boolean(props.errorCount ?? true);
  const { rootMatchString } = props;
  const { pathname } = useLocation();
  const [state, dispatch] = useReducer(reducer, _.cloneDeep(DefaultState));
  const { formRootPath, items } = state;
  const value = useMemo(() => ({ state: state, dispatch: dispatch }), [state, dispatch]);

  useEffect(() => {
    // I possibili match sono ordinati per priorità (è fondamentale).
    const matchStrings = [
      'entities/:resource/create/*',
      ':resource/create/*',
      'entities/:resource/:id/show/*',
      ':resource/:id/show/*',
      'entities/:resource/:id/*',
      ':resource/:id/*'
    ];
    if (rootMatchString !== undefined) {
      matchStrings.push(rootMatchString);
    }
    const match = _.chain(matchStrings)
      .map((s) => matchPath(s, pathname))
      .reject((m) => m === null)
      .first()
      .value();
    const pathnameBase = match?.pathnameBase;

    if (pathnameBase !== undefined && pathnameBase !== formRootPath) {
      dispatch({
        type: ActionType.SET_FORM_ROOT_PATH,
        payload: pathnameBase
      });
    }
  }, [pathname, formRootPath, rootMatchString]);

  useEffect(() => {
    dispatch({
      type: ActionType.SET_SYNC_WITH_LOCATION,
      payload: Boolean(syncWithLocation)
    });
  }, [syncWithLocation, dispatch]);

  useEffect(() => {
    dispatch({
      type: ActionType.SET_ERROR_COUNT,
      payload: Boolean(errorCount)
    });
  }, [errorCount, dispatch]);

  useEffect(() => {
    if (syncWithLocation && formRootPath !== undefined) {
      let locationItem = pathname.replace(formRootPath, '').replace(new RegExp(/^\/?/), '');
      if (_.isEmpty(locationItem)) {
        locationItem =
          _.chain(items)
            .filter((item) => getChildren(item.id, items).length === 0)
            .orderBy([(item) => getLevel(item.id), (item) => item.index])
            .first()
            .value()?.id ?? undefined;
      }
      dispatch({
        type: ActionType.SET_ACTIVE_ITEM,
        payload: locationItem
      });
    }
  }, [dispatch, formRootPath, pathname, items, syncWithLocation]);

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

function useFormContext(): IContext {
  const context = useContext(Context);

  if (context === undefined) {
    throw new Error(
      '[LongForm] useFormContext: context undefined. Please provide a valid Context using LongForm.Provider'
    );
  }

  return context;
}

function useFormState(): IState {
  const context = useFormContext();
  return context.state;
}

function useFormDispatch(): IDispatch {
  const context = useFormContext();
  return context.dispatch;
}

function useFormRootPath(): string | undefined {
  const state = useFormState();
  return state.formRootPath;
}

function useActiveItem(): string | undefined {
  const state = useFormState();
  return state.activeItem;
}

function useItems(): Array<IItem> {
  const state = useFormState();
  return state.items;
}

function useSyncWithLocation(): boolean {
  const state = useFormState();
  return state.syncWithLocation;
}

function useErrorCount(): boolean {
  const state = useFormState();
  return state.errorCount;
}

function useSetActiveItem(): (activeItem: string) => void {
  const dispatch = useFormDispatch();
  const setActiveItem = useCallback(
    (activeItem: string) => {
      dispatch({
        type: ActionType.SET_ACTIVE_ITEM,
        payload: activeItem
      });
    },
    [dispatch]
  );

  return setActiveItem;
}

function useAddItem(): (item: IItem) => void {
  const dispatch = useFormDispatch();
  const addItem = useCallback(
    (item: IItem) => {
      dispatch({
        type: ActionType.ADD_ITEM,
        payload: item
      });
    },
    [dispatch]
  );

  return addItem;
}

function useRemoveItem(): (item: string | IItem) => void {
  const dispatch = useFormDispatch();
  const removeItem = useCallback(
    (item: string | IItem) => {
      dispatch({
        type: ActionType.REMOVE_ITEM,
        payload: item
      });
    },
    [dispatch]
  );

  return removeItem;
}

export type { IProviderProps };
export {
  Provider,
  useActiveItem,
  useAddItem,
  useErrorCount,
  useFormRootPath,
  useItems,
  useRemoveItem,
  useSetActiveItem,
  useSyncWithLocation
};
