import { isEmpty } from 'lodash-es';
import { handleActions } from 'redux-actions';

import { getLines } from '@freecodecamp/shared/utils/get-lines';
import { getTargetEditor } from '../utils/get-target-editor';
import { actionTypes, ns } from './action-types';
import codeStorageEpic from './code-storage-epic';
import completionEpic from './completion-epic';
import createQuestionEpic from './create-question-epic';
import { createCurrentChallengeSaga } from './current-challenge-saga';
import { createAskSocratesSaga } from './ask-socrates-saga';
import { createExecuteChallengeSaga } from './execute-challenge-saga';

export { ns };

const initialState = {
  canFocusEditor: true,
  attempts: 0,
  visibleEditors: {},
  challengeFiles: [],
  challengeMeta: {
    superBlock: '',
    block: '',
    blockHashSlug: '/',
    id: '',
    isLastChallengeInBlock: false,
    nextChallengePath: '/',
    prevChallengePath: '/',
    challengeType: -1,
    saveSubmissionToDB: false,
    description: ''
  },
  challengeTests: [],
  consoleOut: [],
  userCompletedExam: null,
  hasCompletedBlock: false,
  isBuildEnabled: true,
  isExecuting: false,
  isResetting: false,
  logsOut: [],
  modal: {
    completion: false,
    help: false,
    video: false,
    reset: false,
    exitExam: false,
    finishExam: false,
    exitQuiz: false,
    finishQuiz: false,
    examResults: false,
    survey: false,
    projectPreview: false,
    shortcuts: false,
    speaking: false
  },
  portalWindow: null,
  showPreviewPortal: false,
  showPreviewPane: true,
  isProjectPreviewLoading: false,
  projectFormValues: {},
  successMessage: 'Happy Coding!',
  isAdvancing: false,
  chapterSlug: '',
  isSubmitting: false,
  socratesHintState: {
    hint: null,
    isLoading: false,
    error: null,
    attempts: null,
    limit: null
  }
};

export const epics = [completionEpic, createQuestionEpic, codeStorageEpic];

export const sagas = [
  ...createExecuteChallengeSaga(actionTypes),
  ...createCurrentChallengeSaga(actionTypes),
  ...createAskSocratesSaga(actionTypes)
];

export const reducer = handleActions(
  {
    [actionTypes.submitChallenge]: state => ({
      ...state,
      isSubmitting: true
    }),
    [actionTypes.submitChallengeComplete]: state => ({
      ...state,
      isSubmitting: false
    }),
    [actionTypes.submitChallengeError]: state => ({
      ...state,
      isSubmitting: false
    }),
    [actionTypes.createFiles]: (state, { payload }) => ({
      ...state,
      challengeFiles: payload.map(challengeFile => ({
        ...challengeFile,
        seed: challengeFile.contents.slice(),
        editableContents: getLines(
          challengeFile.contents,
          challengeFile.editableRegionBoundaries
        ),
        editableRegionBoundaries:
          challengeFile.editableRegionBoundaries?.slice() ?? [],
        seedEditableRegionBoundaries:
          challengeFile.editableRegionBoundaries?.slice() ?? []
      }))
    }),
    [actionTypes.updateFile]: (
      state,
      { payload: { fileKey, contents, editableRegionBoundaries } }
    ) => {
      const updates = {};
      // if a given part of the payload is null, we leave that part of the state
      // unchanged
      if (editableRegionBoundaries !== null)
        updates.editableRegionBoundaries = editableRegionBoundaries;
      if (contents !== null) updates.contents = contents;
      if (editableRegionBoundaries !== null && contents !== null)
        updates.editableContents = getLines(contents, editableRegionBoundaries);
      return {
        ...state,
        challengeFiles: state.challengeFiles.map(challengeFile =>
          challengeFile.fileKey === fileKey
            ? { ...challengeFile, ...updates }
            : { ...challengeFile }
        ),
        isBuildEnabled: true
      };
    },
    [actionTypes.initTests]: (state, { payload }) => ({
      ...state,
      challengeTests: payload
    }),
    [actionTypes.initHooks]: (state, { payload }) => ({
      ...state,
      challengeHooks: payload
    }),
    [actionTypes.updateTests]: (state, { payload }) => ({
      ...state,
      challengeTests: payload
    }),
    [actionTypes.initConsole]: (state, { payload }) => ({
      ...state,
      consoleOut: payload ? [payload] : []
    }),
    [actionTypes.updateConsole]: (state, { payload }) => ({
      ...state,
      consoleOut: state.consoleOut.concat(payload)
    }),
    [actionTypes.initLogs]: state => ({
      ...state,
      logsOut: []
    }),
    [actionTypes.updateLogs]: (state, { payload }) => ({
      ...state,
      logsOut: state.logsOut.concat(payload)
    }),
    [actionTypes.logsToConsole]: (state, { payload }) => ({
      ...state,
      consoleOut: isEmpty(state.logsOut)
        ? state.consoleOut
        : state.consoleOut.concat(payload, state.logsOut)
    }),
    [actionTypes.initVisibleEditors]: state => {
      let persistingVisibleEditors = {};
      const prevVisibleEditorKeys = Object.keys(state.visibleEditors);
      if (prevVisibleEditorKeys.length > 1) {
        // Restore states of relevant visible editors for the current challengeFiles
        persistingVisibleEditors = prevVisibleEditorKeys
          .filter(editorKey => {
            return state.challengeFiles.find(
              challengeFile => challengeFile.fileKey === editorKey
            );
          })
          .reduce((visibleEditors, key) => {
            visibleEditors[key] = state.visibleEditors[key];
            return visibleEditors;
          }, {});
      }
      return {
        ...state,
        visibleEditors: {
          ...persistingVisibleEditors,
          [getTargetEditor(state.challengeFiles)]: true
        }
      };
    },
    [actionTypes.updateChallengeMeta]: (state, { payload }) => ({
      ...state,
      challengeMeta: { ...payload }
    }),
    [actionTypes.resetChallenge]: state => {
      const challengeFilesReset = state.challengeFiles.map(challengeFile => ({
        ...challengeFile,
        contents: challengeFile.seed.slice(),
        editableContents: getLines(
          challengeFile.seed,
          challengeFile.seedEditableRegionBoundaries
        ),
        editableRegionBoundaries:
          challengeFile.seedEditableRegionBoundaries.slice()
      }));
      return {
        ...state,
        currentTab: 2,
        challengeFiles: challengeFilesReset,
        challengeTests: state.challengeTests.map(({ text, testString }) => ({
          text,
          testString
        })),
        consoleOut: [],
        isResetting: true,
        attempts: 0
      };
    },
    [actionTypes.resetAttempts]: state => ({
      ...state,
      attempts: 0
    }),
    [actionTypes.stopResetting]: state => ({
      ...state,
      isResetting: false
    }),
    [actionTypes.updateSolutionFormValues]: (state, { payload }) => ({
      ...state,
      projectFormValues: payload
    }),
    [actionTypes.disableBuildOnError]: state => ({
      ...state,
      isBuildEnabled: false
    }),
    [actionTypes.setShowPreviewPortal]: (state, { payload }) => ({
      ...state,
      showPreviewPortal: payload
    }),
    [actionTypes.setShowPreviewPane]: (state, { payload }) => ({
      ...state,
      showPreviewPane: payload
    }),
    [actionTypes.storePortalWindow]: (state, { payload }) => ({
      ...state,
      portalWindow: payload
    }),
    [actionTypes.removePortalWindow]: state => ({
      ...state,
      portalWindow: null
    }),
    [actionTypes.updateSuccessMessage]: (state, { payload }) => ({
      ...state,
      successMessage: payload
    }),
    [actionTypes.setIsAdvancing]: (state, { payload }) => ({
      ...state,
      isAdvancing: payload
    }),
    [actionTypes.setChapterSlug]: (state, { payload }) => ({
      ...state,
      chapterSlug: payload
    }),
    [actionTypes.setUserCompletedExam]: (state, { payload }) => ({
      ...state,
      userCompletedExam: payload
    }),
    [actionTypes.closeModal]: (state, { payload }) => ({
      ...state,
      modal: {
        ...state.modal,
        [payload]: false
      }
    }),
    [actionTypes.openModal]: (state, { payload }) => {
      const isProjectPreviewModal = payload === 'projectPreview';

      return {
        ...state,
        modal: {
          ...state.modal,
          [payload]: true
        },
        isProjectPreviewLoading: isProjectPreviewModal
          ? true
          : state.isProjectPreviewLoading
      };
    },
    [actionTypes.setProjectPreviewLoading]: (state, { payload }) => ({
      ...state,
      isProjectPreviewLoading: payload
    }),
    [actionTypes.executeChallenge]: state => ({
      ...state,
      currentTab: 3,
      attempts: state.attempts + 1,
      isExecuting: true
    }),
    [actionTypes.executeChallengeComplete]: state => ({
      ...state,
      isExecuting: false
    }),
    [actionTypes.askSocrates]: state => ({
      ...state,
      socratesHintState: {
        hint: null,
        isLoading: true,
        error: null,
        attempts: state.socratesHintState.attempts,
        limit: state.socratesHintState.limit
      }
    }),
    [actionTypes.askSocratesComplete]: (state, { payload }) => ({
      ...state,
      socratesHintState: {
        hint: payload.hint,
        isLoading: false,
        error: null,
        attempts: payload.attempts,
        limit: payload.limit
      }
    }),
    [actionTypes.askSocratesError]: (state, { payload }) => ({
      ...state,
      socratesHintState: {
        hint: null,
        isLoading: false,
        error: payload.error,
        attempts: payload.attempts ?? state.socratesHintState.attempts,
        limit: payload.limit ?? state.socratesHintState.limit
      }
    }),
    [actionTypes.setEditorFocusability]: (state, { payload }) => ({
      ...state,
      canFocusEditor: payload
    }),
    [actionTypes.toggleVisibleEditor]: (state, { payload }) => {
      return {
        ...state,
        visibleEditors: {
          ...state.visibleEditors,
          [payload]: !state.visibleEditors[payload]
        }
      };
    },
    [actionTypes.createQuestion]: (state, { payload }) => ({
      ...state,
      description: payload
    })
  },
  initialState
);
