import {
	DegreePlannerState,
	INITIAL_DEGREE_PLANNER_STATE
} from '@app/degree-planner/state';
import {
	PlanActionTypes,
	InitialPlanLoadResponse,
	ChangeVisiblePlanResponse,
	RemoveCourseResponse,
	ChangeCourseTermResponse,
	AddCourseResponse,
	RemoveSavedForLaterResponse,
	AddSavedForLaterResponse
} from '@app/degree-planner/actions/plan.actions';
import {
	NoteActionTypes,
	WriteNoteResponse,
	DeleteNoteResponse
} from '@app/degree-planner/actions/note.actions';

type SupportedActions =
	| InitialPlanLoadResponse
	| ChangeVisiblePlanResponse
	| WriteNoteResponse
	| DeleteNoteResponse
	| ChangeCourseTermResponse
	| RemoveCourseResponse
	| AddCourseResponse
	| RemoveSavedForLaterResponse
	| AddSavedForLaterResponse;

export function degreePlannerReducer(
	state = INITIAL_DEGREE_PLANNER_STATE,
	action: SupportedActions
): DegreePlannerState {
	switch (action.type) {
		/**
		 * The `InitialPlanLoadResponse` action is triggered on initial Degree
		 * Planner app load. It downloads a list of the user's degree plans and
		 * picks the primary plan from that list to load as the first visible plan.
		 */
		case PlanActionTypes.InitialPlanLoadResponse: {
			return { ...action.payload };
		}

		/**
		 * The `ChangeVisiblePlanResponse` action is triggered whenever the UI needs
		 * to switch which degree plan is being shown and load the data associated
		 * with that degree plan. The reducer extracts that downloaded data from the
		 * action payload and builds a new state using that data.
		 */
		case PlanActionTypes.ChangeVisiblePlanResponse: {
			return { ...state, ...action.payload };
		}

		/**
		 * The `WriteNoteResponse` action is dispatched by the `Note.write$` effect
		 * upon a successful response from the `updateNote` or `createNote` API
		 * endpoints. The reducer in this case either:
		 *
		 *  - Replaces a note on a term that already had a note.
		 *  - *OR* adds a note to a term that didn't previously have a note.
		 *  - *OR* adds a new term with the given note if no term exists with the note's termCode.
		 */
		case NoteActionTypes.WriteNoteResponse: {
			const updatedNote = action.payload.updatedNote;
			const updatedTermCode = updatedNote.termCode;
			const originalTerms = state.visibleTerms;
			if (termCodeExists(updatedTermCode, originalTerms)) {
				/**
				 * If a term with the given `termCode` *DOES exist* in the state,
				 * replace just that term with the new data inside the action.
				 */
				const newVisibleTerms = originalTerms.map(term => {
					if (term.termCode === updatedTermCode) {
						return { ...term, note: updatedNote };
					} else {
						return term;
					}
				});

				return { ...state, visibleTerms: newVisibleTerms };
			} else {
				/**
				 * If a term with the given `termCode` *DOES NOT exist*
				 * in the state, add it to the end of the term list.
				 */
				const newVisibleTerms = originalTerms.concat({
					termCode: updatedTermCode,
					note: updatedNote,
					courses: []
				});

				return { ...state, visibleTerms: newVisibleTerms };
			}
		}

		/**
		 * The `DeleteNoteResponse` action is dispatched after the `deleteNote` API
		 * has been called and it is okay to remote the note with the given
		 * termCode from the degree planner state.
		 */
		case NoteActionTypes.DeleteNoteResponse: {
			const deletedTermCode = action.payload.termCode;
			const originalTerms = state.visibleTerms;
			if (termCodeExists(deletedTermCode, originalTerms)) {
				/**
				 * If a term with the given `termCode` *DOES EXIST* in the state,
				 * remove that term's note.
				 */
				const newVisibleTerms = originalTerms.map(term => {
					if (term.termCode === deletedTermCode) {
						return { ...term, note: undefined };
					} else {
						return term;
					}
				});

				return { ...state, visibleTerms: newVisibleTerms };
			} else {
				return state;
			}
		}

		case PlanActionTypes.ChangeCourseTermResponse: {
			const {to, from, id} = action.payload;
			const t = state.visibleTerms.find(term => term.termCode === from);

			if (t) {
				const course = t.courses.find(c => c.id === id);

				if (course) {
					course.termCode = to;

					// Create new visibleTerms array
					const newVisibleTerms = state.visibleTerms.map(term => {
						if (term.termCode === from) {
							// Remove the course from the previous term
							term.courses = term.courses.filter(c => c.id !== id);
						} else if (term.termCode === to) {
							// Add the new course to this term
							term.courses = [...term.courses, course];
						}
						return term;

					});

					return {...state, visibleTerms: newVisibleTerms};
				}

				return state;
			}
			return state;
		}

		case PlanActionTypes.AddCourseResponse: {
			const { course } = action.payload;

			const newVisibleTerms = state.visibleTerms.map(term => {
				if (term.termCode === course.termCode) {
					term.courses.push(course);
				}
				return term;
			});

			return {...state, visibleTerms: newVisibleTerms};

			// return {...state, visibleTerms: newVisibleTerms};
			// return state;
		}

		case PlanActionTypes.RemoveCourseResponse: {
			const { id } = action.payload;

			// Create new visibleTerms array
			const newVisibleTerms = state.visibleTerms.map(term => {
				term.courses = term.courses.filter(course => {
					return course.id !== id;
				});
				return term;
			});

			return {...state, visibleTerms: newVisibleTerms};
		}

		case PlanActionTypes.RemoveSavedForLaterResponse: {
			const { courseId, subjectCode } = action.payload;

			// // Create new saved for later array
			const newSavedForLater = state.savedForLaterCourses.filter(
				course => course.subjectCode !== subjectCode && course.courseId !== courseId
			);

			return {...state, savedForLaterCourses: newSavedForLater};
		}

		case PlanActionTypes.AddSavedForLaterResponse: {
			const { courseId, subjectCode } = action.payload;

			// // Create new saved for later array
			const newSavedForLater = [
				...state.savedForLaterCourses,
				// TODO Update this when the API is fixed, the API should be sending a fav course as a response
				{
					id: null,
					courseId,
					subjectCode,
					topicId: 0,
					courseOrder: 0,
					catalogNumber: '',
					title: '',
					termCode: '0000'
				}
			];

			return {...state, savedForLaterCourses: newSavedForLater};
		}

		/**
		 * It's okay if the action didn't match any of the cases above. If that's
		 * the case, just return the existing state object.
		 */
		default:
			return state;
	}
}

const termCodeExists = (termCode: string, things: { termCode: string }[]) => {
	return things.some(thing => thing.termCode === termCode);
};