import { logger } from 'tachyon-logger';
import { Dispatch } from 'redux';

import { GQLError } from 'mweb/common/fetch/fetchGQL';
import { fetchGames, GamesDataPayload } from 'mweb/common/fetch/games';
import {
  RootState,
  doesGameDirectoryDataNeedReinitialization,
  areAllGamesLoaded,
} from 'mweb/common/reducers/root';

export const GAMES_DATA_REINITIALIZED_ACTION_TYPE =
  'GAMES_DATA_REINITIALIZED_ACTION_TYPE';
export interface GamesDataReinitializedAction {
  type: typeof GAMES_DATA_REINITIALIZED_ACTION_TYPE;
}

export const GAMES_DATA_PAGE_LOADED_ACTION_TYPE =
  'GAMES_DATA_PAGE_LOADED_ACTION_TYPE';
export interface GamesDataPageLoadedAction {
  type: typeof GAMES_DATA_PAGE_LOADED_ACTION_TYPE;
  payload: GamesDataPayload;
}

export const GAMES_DATA_PAGE_FAILED_ACTION_TYPE =
  'GAMES_DATA_PAGE_FAILED_ACTION_TYPE';
export interface GamesDataPageFailedAction {
  type: typeof GAMES_DATA_PAGE_FAILED_ACTION_TYPE;
  payload: GQLError;
}

export type GamesDataAction =
  | GamesDataReinitializedAction
  | GamesDataPageLoadedAction
  | GamesDataPageFailedAction;

interface GamesDataFetchNextPageOpts {
  isOnePageEnough?: boolean;
}

export const GAME_DIRECTORY_CACHE_TTL = 10 * 60 * 1000; // 10 minutes
export function gamesDataGetPage(
  opts: GamesDataFetchNextPageOpts = {},
): (dispatch: Dispatch<RootState>, getState: () => RootState) => Promise<void> {
  return async (dispatch, getState) => {
    const state = getState();
    let cursor = null;
    if (
      doesGameDirectoryDataNeedReinitialization(state, GAME_DIRECTORY_CACHE_TTL)
    ) {
      logger.info('Reinitializing games data and loading first page');
      dispatch(gamesDataReinitialize());
    } else if (opts.isOnePageEnough) {
      // if we don't need re-init, we have at least a page of data
      logger.info('Skipping games data request as initial data already loaded');
      return;
    } else if (areAllGamesLoaded(state)) {
      logger.info('Skipping games data request as all games already loaded');
      return;
    } else {
      logger.info('Attempting to fetch next page of games data');
      cursor = state.data.games.gamesLoadStatus.lastGameCursor;
    }

    return dispatch(gamesDataFetchPage(cursor));
  };
}

export function gamesDataFetchPage(
  cursor: string | null,
): (dispatch: Dispatch<RootState>) => Promise<void> {
  return async dispatch => {
    logger.info(`Fetching games data starting after ${cursor || 'beginning'}`);
    try {
      const data = await fetchGames({ cursor });
      dispatch(gamesDataLoadPage(data));
    } catch (gamesDataFetchPageError) {
      logger.warn({ gamesDataFetchPageError });
      dispatch(gamesDataFailPage(gamesDataFetchPageError));
    }
  };
}

export function gamesDataReinitialize(): GamesDataReinitializedAction {
  return {
    type: GAMES_DATA_REINITIALIZED_ACTION_TYPE,
  };
}

export function gamesDataLoadPage(
  payload: GamesDataPayload,
): GamesDataPageLoadedAction {
  return {
    type: GAMES_DATA_PAGE_LOADED_ACTION_TYPE,
    payload,
  };
}

export function gamesDataFailPage(error: GQLError): GamesDataPageFailedAction {
  return {
    type: GAMES_DATA_PAGE_FAILED_ACTION_TYPE,
    payload: error,
  };
}

// Tracked event

export interface GamesDataPageLoadedEventPayload {
  gamesLoaded: number;
}

export const GAMES_DATA_PAGE_LOADED_EVENT_TYPE =
  'GAMES_DATA_PAGE_LOADED_EVENT_TYPE';
export interface GamesDataPageLoadedEvent {
  type: typeof GAMES_DATA_PAGE_LOADED_EVENT_TYPE;
  payload: GamesDataPageLoadedEventPayload;
}
