import { nanoid } from 'nanoid/non-secure';
import type {
  PartialState,
  CommonNavigationAction,
  Router,
  ParamListBase,
} from './types';
import TabRouter, {
  TabActions,
  TabActionType,
  TabRouterOptions,
  TabNavigationState,
  TabActionHelpers,
} from './TabRouter';

export type DrawerActionType =
  | TabActionType
  | {
      type: 'OPEN_DRAWER' | 'CLOSE_DRAWER' | 'TOGGLE_DRAWER';
      source?: string;
      target?: string;
    };

export type DrawerRouterOptions = TabRouterOptions & {
  openByDefault?: boolean;
};

export type DrawerNavigationState<ParamList extends ParamListBase> = Omit<
  TabNavigationState<ParamList>,
  'type' | 'history'
> & {
  /**
   * Type of the router, in this case, it's drawer.
   */
  type: 'drawer';
  /**
   * List of previously visited route keys and drawer open status.
   */
  history: ({ type: 'route'; key: string } | { type: 'drawer' })[];
};

export type DrawerActionHelpers<
  ParamList extends ParamListBase
> = TabActionHelpers<ParamList> & {
  /**
   * Open the drawer sidebar.
   */
  openDrawer(): void;

  /**
   * Close the drawer sidebar.
   */
  closeDrawer(): void;

  /**
   * Open the drawer sidebar if closed, or close if opened.
   */
  toggleDrawer(): void;
};

export const DrawerActions = {
  ...TabActions,
  openDrawer(): DrawerActionType {
    return { type: 'OPEN_DRAWER' };
  },
  closeDrawer(): DrawerActionType {
    return { type: 'CLOSE_DRAWER' };
  },
  toggleDrawer(): DrawerActionType {
    return { type: 'TOGGLE_DRAWER' };
  },
};

const isDrawerOpen = (
  state:
    | DrawerNavigationState<ParamListBase>
    | PartialState<DrawerNavigationState<ParamListBase>>
) => Boolean(state.history?.some((it) => it.type === 'drawer'));

const openDrawer = (
  state: DrawerNavigationState<ParamListBase>
): DrawerNavigationState<ParamListBase> => {
  if (isDrawerOpen(state)) {
    return state;
  }

  return {
    ...state,
    history: [...state.history, { type: 'drawer' }],
  };
};

const closeDrawer = (
  state: DrawerNavigationState<ParamListBase>
): DrawerNavigationState<ParamListBase> => {
  if (!isDrawerOpen(state)) {
    return state;
  }

  return {
    ...state,
    history: state.history.filter((it) => it.type !== 'drawer'),
  };
};

export default function DrawerRouter({
  openByDefault,
  ...rest
}: DrawerRouterOptions): Router<
  DrawerNavigationState<ParamListBase>,
  DrawerActionType | CommonNavigationAction
> {
  const router = (TabRouter(rest) as unknown) as Router<
    DrawerNavigationState<ParamListBase>,
    TabActionType | CommonNavigationAction
  >;

  return {
    ...router,

    type: 'drawer',

    getInitialState({ routeNames, routeParamList, routeGetIdList }) {
      let state = router.getInitialState({
        routeNames,
        routeParamList,
        routeGetIdList,
      });

      if (openByDefault) {
        state = openDrawer(state);
      }

      return {
        ...state,
        stale: false,
        type: 'drawer',
        key: `drawer-${nanoid()}`,
      };
    },

    getRehydratedState(
      partialState,
      { routeNames, routeParamList, routeGetIdList }
    ) {
      if (partialState.stale === false) {
        return partialState;
      }

      let state = router.getRehydratedState(partialState, {
        routeNames,
        routeParamList,
        routeGetIdList,
      });

      if (partialState.history ? isDrawerOpen(partialState) : openByDefault) {
        state = openDrawer(state);
      }

      return {
        ...state,
        type: 'drawer',
        key: `drawer-${nanoid()}`,
      };
    },

    getStateForRouteFocus(state, key) {
      const result = router.getStateForRouteFocus(state, key);

      if (openByDefault) {
        return openDrawer(result);
      }

      return closeDrawer(result);
    },

    getStateForAction(state, action, options) {
      switch (action.type) {
        case 'OPEN_DRAWER':
          return openDrawer(state);

        case 'CLOSE_DRAWER':
          return closeDrawer(state);

        case 'TOGGLE_DRAWER':
          if (isDrawerOpen(state)) {
            return closeDrawer(state);
          }

          return openDrawer(state);

        case 'GO_BACK':
          if (openByDefault) {
            if (!isDrawerOpen(state)) {
              return openDrawer(state);
            }
          } else {
            if (isDrawerOpen(state)) {
              return closeDrawer(state);
            }
          }

          return router.getStateForAction(state, action, options);

        default:
          return router.getStateForAction(state, action, options);
      }
    },

    actionCreators: DrawerActions,
  };
}
