import * as R from "ramda";
import { v4 } from "uuid";

import {
  firstStackEntriesSelector,
  previousStackEntriesSelector,
} from "./selectors";

/* initial state */

const MAXIMIZED = "MAXIMIZED";
const MINIMIZED = "MINIMIZED";
const FULLSCREEN = "FULLSCREEN";
const PIP = "PIP";

export const initialState: NavigationReducerState = {
  stack: {
    mainStack: [],
    modal: null,
  },
  location: { key: "", pathname: "/", state: null },
  options: {
    videoModal: {
      visible: false,
      mode: MAXIMIZED, // VideoModalMode => MAXIMIZED | MINIMIZED | FULLSCREEN
      previousMode: undefined,
      item: null,
    },
  },
};

/* Actions */

export enum ACTIONS {
  PUSH = "PUSH",
  REPLACE = "REPLACE",
  BACK = "POP",
  BACK_PLAYER_NESTED_CONTENT = "BACK_PLAYER_NESTED_CONTENT",
  REPLACE_TOP = "REPLACE_TOP",
  SET_NESTED_CONTENT = "SET_NESTED_CONTENT",
  SET_LOCATION = "SET_LOCATION",
  OPEN_VIDEO_MODAL = "OPEN_VIDEO_MODAL",
  CLOSE_VIDEO_MODAL = "CLOSE_VIDEO_MODAL",
  MINIMISE_VIDEO_MODAL = "MINIMISE_VIDEO_MODAL",
  MAXIMISE_VIDEO_MODAL = "MAXIMISE_VIDEO_MODAL",
  FULLSCREEN_VIDEO_MODAL = "FULLSCREEN_VIDEO_MODAL",
  SET_VIDEO_MODAL_ITEM = "SET_VIDEO_MODAL_ITEM",
  PIP = "PIP",
}

type ActionType = typeof ACTIONS;
type ActionKeys = keyof ActionType;
type ActionValues = ActionType[ActionKeys];

/* Navigation Reducer */

const navigationAction = ({ type, route, state = {} }) => ({
  type,
  payload: { route, ...state, key: v4() },
});

export const push = (route, state) =>
  navigationAction({
    type: ACTIONS.PUSH,
    route,
    state,
  });

export const replace = (route, state) =>
  navigationAction({
    type: ACTIONS.REPLACE,
    route,
    state,
  });

export const back = ({
  route = "",
  state,
}: Partial<NavigationScreenState> = {}) =>
  navigationAction({
    type: ACTIONS.BACK,
    route,
    state,
  });

export const backAndReplacePlayer = ({
  route = "",
  state,
}: Partial<NavigationScreenState> = {}) =>
  navigationAction({
    type: ACTIONS.BACK_PLAYER_NESTED_CONTENT,
    route,
    state,
  });

export const setReplaceTop = (route, state) =>
  navigationAction({
    type: ACTIONS.REPLACE_TOP,
    route,
    state,
  });

export const setBackPlayerNestedContent = (route, state) =>
  navigationAction({
    type: ACTIONS.BACK_PLAYER_NESTED_CONTENT,
    route,
    state,
  });

export const setNestedContent = (
  pathname: string,
  entry: ZappEntry,
  screen: ZappRiver
) =>
  navigationAction({
    type: ACTIONS.SET_NESTED_CONTENT,
    route: `${pathname}/river/${screen.id}`,
    state: { entry, screen },
  });

const navigationEntry = (
  type: ActionValues,
  { route, screen, entry, key, options }: AnyRecord
) => ({
  key,
  action: type,
  route,
  state: { screen, entry, options },
});

function updatePopActions(state) {
  return R.map(
    R.when(
      R.propEq("action", ACTIONS.BACK),
      R.assoc("action", ACTIONS.PUSH) || R.assoc("action", ACTIONS.REPLACE_TOP)
    )
  )(state);
}

function removeTop(state, payload) {
  let playNextState = [...state.mainStack];

  const preloadHooks =
    payload.entry?.targetScreen?.hooks?.preload_plugins || [];

  const postloadHooks =
    payload.entry?.targetScreen?.hooks?.postload_plugins || [];

  const hooksScreenIds = new Set([
    ...preloadHooks.map((hook) => hook.screen_id),
    ...postloadHooks.map((hook) => hook.screen_id),
  ]);

  playNextState = playNextState.filter(
    (item) => !hooksScreenIds.has(item.state.screen.id)
  );

  playNextState.pop();

  return playNextState;
}

function backPlayerNestedContent(state) {
  const currentState = [...state.mainStack];

  const findPlayableAndTruncate = (arr) => {
    const index = R.findIndex(R.pipe(R.prop("route"), R.includes("playable")))(
      arr
    );

    return index !== -1 ? R.dropLast(arr.length - index - 1, arr) : [null, arr];
  };

  return findPlayableAndTruncate(currentState);
}

function addNestedContent(stack, payload, locationState: LocationReducerState) {
  const locationKey = locationState.key;
  const stackIndex = R.findIndex(R.propEq("key", locationKey))(stack);

  return R.adjust(
    stackIndex,
    R.compose(
      R.assocPath(["state", "nested"], payload.state),
      R.assoc("stack", payload)
    ),
    stack
  );
}

function navigationStackReducer(
  state: NavigationStackReducerState = initialState.stack,
  { type = "", payload, meta }: ReducerAction = { type: "" }
) {
  switch (type) {
    case ACTIONS.SET_NESTED_CONTENT:
      return {
        ...state,
        mainStack: addNestedContent(
          state.mainStack,
          navigationEntry(ACTIONS.REPLACE, payload as AnyRecord),
          meta as LocationReducerState
        ),
      };
    case ACTIONS.PUSH:
      return {
        ...state,
        mainStack: [
          ...updatePopActions(state.mainStack),
          navigationEntry(ACTIONS.PUSH, payload as AnyRecord),
        ],
      };
    case ACTIONS.REPLACE_TOP:
      return {
        ...state,
        mainStack: [
          ...removeTop(state, payload),
          navigationEntry(ACTIONS.PUSH, payload as AnyRecord),
        ],
      };
    case ACTIONS.REPLACE:
      return {
        ...state,
        mainStack: [navigationEntry(ACTIONS.REPLACE, payload as AnyRecord)],
      };
    case ACTIONS.BACK:
      if (state.mainStack.length > 1) {
        return {
          ...state,
          mainStack: R.compose(
            R.adjust(-1, R.assoc("action", ACTIONS.BACK)),
            (payload as { route }).route
              ? firstStackEntriesSelector
              : previousStackEntriesSelector,
            R.assoc(["stack"], R.__, {})
          )(state.mainStack),
        };
      }

      return state;
    case ACTIONS.BACK_PLAYER_NESTED_CONTENT:
      return {
        ...state,
        mainStack: backPlayerNestedContent(state),
      };
    case ACTIONS.OPEN_VIDEO_MODAL:
      return {
        ...state,
        modal: navigationEntry(ACTIONS.OPEN_VIDEO_MODAL, payload as AnyRecord),
      };

    case ACTIONS.CLOSE_VIDEO_MODAL:
      return {
        ...state,
        modal: null,
      };

    case ACTIONS.SET_VIDEO_MODAL_ITEM:
      return {
        ...state,
        modal: {
          ...state.modal,
          entry: payload as ZappEntry,
        },
      };
    default:
      return state;
  }
}

function locationReducer(
  state = initialState.location,
  { type, payload, meta = {} }
) {
  const { route, key, screen, entry, options } = payload;

  switch (type) {
    case ACTIONS.BACK:
      // eslint-disable-next-line no-case-declarations
      const previousEntry = R.compose(
        R.last,
        payload.route ? firstStackEntriesSelector : previousStackEntriesSelector
      )(meta);

      if (previousEntry) {
        const { state, key, action } = previousEntry;

        return { pathname: previousEntry.route, state, key, action };
      }

      return state;

    case ACTIONS.PUSH:
    case ACTIONS.REPLACE:
    case ACTIONS.REPLACE_TOP:
    case ACTIONS.BACK_PLAYER_NESTED_CONTENT:
      return { pathname: route, state: { screen, entry, options }, key };

    case ACTIONS.SET_NESTED_CONTENT:
      // eslint-disable-next-line no-case-declarations
      const nestedEntry = navigationEntry(
        ACTIONS.REPLACE,
        payload as AnyRecord
      );

      return R.assocPath(["state", "nested"], nestedEntry.state, state);
    default:
      return state;
  }
}

/* Video Modal reducer */
export const setVideoModalOpen = (item, options, screen) => ({
  type: ACTIONS.OPEN_VIDEO_MODAL,
  payload: {
    item,
    options,
    screen,
    entry: item,
    route: "videoModal",
    key: v4(),
  },
});

export const setVideoModalClose = () => ({
  type: ACTIONS.CLOSE_VIDEO_MODAL,
});

export const setVideoModalFullscreen = () => ({
  type: ACTIONS.FULLSCREEN_VIDEO_MODAL,
});

export const setVideoPiP = () => ({
  type: ACTIONS.PIP,
});

export const setVideoModalMaximized = () => ({
  type: ACTIONS.MAXIMISE_VIDEO_MODAL,
});

export const setVideoModalMinimized = () => ({
  type: ACTIONS.MINIMISE_VIDEO_MODAL,
});

export const setVideoModalItem = (payload) => ({
  type: ACTIONS.SET_VIDEO_MODAL_ITEM,
  payload,
});

export const videoModalReducer = (
  state = initialState.options.videoModal,
  { type, payload }
) => {
  switch (type) {
    case ACTIONS.OPEN_VIDEO_MODAL: {
      const { item, options } = payload;

      const mode = R.pathOr(MAXIMIZED, ["mode"], options);

      return R.compose(
        R.set(R.lensProp("mode"), mode),
        R.set(R.lensProp("visible"), true),
        R.over(
          R.lensProp("item"),
          R.unless(R.tryCatch(R.propEq("id", item?.id), R.F), () => item)
        )
      )(state);
    }

    case ACTIONS.SET_VIDEO_MODAL_ITEM:
      return R.set(R.lensProp("item"), payload)(state);
    case ACTIONS.CLOSE_VIDEO_MODAL:
      return initialState.options.videoModal;
    case ACTIONS.FULLSCREEN_VIDEO_MODAL:
      return R.compose(
        R.set(R.lensProp("mode"), FULLSCREEN),
        R.set(R.lensProp("previousMode"), state.mode)
      )(state);
    case ACTIONS.MINIMISE_VIDEO_MODAL:
      return R.compose(
        R.set(R.lensProp("mode"), MINIMIZED),
        R.set(R.lensProp("previousMode"), state.mode)
      )(state);
    case ACTIONS.MAXIMISE_VIDEO_MODAL:
      return R.compose(
        R.set(R.lensProp("mode"), MAXIMIZED),
        R.set(R.lensProp("previousMode"), state.mode)
      )(state);
    case ACTIONS.PIP:
      return R.compose(
        R.set(R.lensProp("mode"), PIP),
        R.set(R.lensProp("previousMode"), state.mode)
      )(state);
  }
};

const applyReducer = (reducer, action) => (state) => reducer(state, action);

export default function navigationReducer(
  state = initialState,
  { type, payload }: ReducerAction = { type: "" }
) {
  switch (type) {
    case ACTIONS.PUSH:
    case ACTIONS.REPLACE:
    case ACTIONS.REPLACE_TOP:
    case ACTIONS.BACK:
    case ACTIONS.BACK_PLAYER_NESTED_CONTENT:
      return R.evolve(
        {
          stack: applyReducer(navigationStackReducer, { type, payload }),
          location: applyReducer(locationReducer, {
            type,
            payload,
            meta: state,
          }),
        },
        state
      );

    case ACTIONS.SET_NESTED_CONTENT:
      return R.evolve(
        {
          stack: applyReducer(navigationStackReducer, {
            type,
            payload,
            meta: state.location,
          }),
          location: applyReducer(locationReducer, {
            type,
            payload,
            meta: state.stack,
          }),
        },
        state
      );

    case ACTIONS.OPEN_VIDEO_MODAL:
    case ACTIONS.CLOSE_VIDEO_MODAL:
    case ACTIONS.SET_VIDEO_MODAL_ITEM:
      return R.evolve(
        {
          options: {
            videoModal: applyReducer(videoModalReducer, { type, payload }),
          },
          stack: applyReducer(navigationStackReducer, { type, payload }),
        },
        state
      );

    case ACTIONS.FULLSCREEN_VIDEO_MODAL:
    case ACTIONS.MAXIMISE_VIDEO_MODAL:
    case ACTIONS.MINIMISE_VIDEO_MODAL:
    case ACTIONS.PIP:
      return R.evolve(
        {
          options: {
            videoModal: applyReducer(videoModalReducer, { type, payload }),
          },
        },
        state
      );

    default:
      return state;
  }
}
