import { Dispatch } from "redux";

import { handleRequest } from "@ax/helpers";
import { menus } from "@ax/api";
import { appActions } from "@ax/containers/App";
import { IMenuItem, IMenu, IRootState, IMenuForm } from "@ax/types";
import {
	SET_ORIGINAL_MENUS,
	SET_EDITOR_MENU,
	SET_ITEM,
	SET_MENU_FORM_DATA,
	SET_ORIGINAL_MENU,
	SET_TOTAL_ITEMS,
	SET_CURRENT_TYPE,
} from "./constants";
import {
	ISetSavedMenus,
	ISetEditorMenu,
	ISetItem,
	ISetMenuForm,
	ISetSavedMenu,
	ISetTotalItems,
	ISetCurrentType,
} from "./interfaces";
import { menuInitialState } from "./reducer";

const { setIsLoading } = appActions;

function setSavedMenus(savedMenus: IMenu[]): ISetSavedMenus {
	return { type: SET_ORIGINAL_MENUS, payload: { savedMenus } };
}

function setSavedMenu(savedMenu: IMenu): ISetSavedMenu {
	return { type: SET_ORIGINAL_MENU, payload: { savedMenu } };
}

function setEditorMenu(editorMenu: IMenu | null): ISetEditorMenu {
	return { type: SET_EDITOR_MENU, payload: { editorMenu } };
}

function setItem(item: IMenuItem): ISetItem {
	return { type: SET_ITEM, payload: { item } };
}

function setMenuFormData(form: IMenuForm): ISetMenuForm {
	return { type: SET_MENU_FORM_DATA, payload: { form } };
}

function setTotalItems(totalItems: number): ISetTotalItems {
	return { type: SET_TOTAL_ITEMS, payload: { totalItems } };
}

function setCurrentType(currentType: string): ISetCurrentType {
	return { type: SET_CURRENT_TYPE, payload: { currentType } };
}

const getStateValues = (getState: () => IRootState) => {
	const { savedMenus, savedMenu, editorMenu, currentType } = getState().menu;
	const { currentSiteInfo } = getState().sites;
	const { isSaving, isLoading, lang } = getState().app;

	return {
		savedMenus,
		savedMenu,
		editorMenu,
		type: currentType,
		currentSiteInfo,
		isLoading,
		isSaving,
		lang,
	};
};

function setEditorMenuContent(menus: IMenu[], menuType: string, dispatch: Dispatch) {
	const menu = menus.find((menu) => menu.name === menuType) || menus[0];

	dispatch(setSavedMenu(menu));
	dispatch(setEditorMenu(menu));
	dispatch(setIsLoading(false));
}

function getMenus(hasLoading = true): (dispatch: Dispatch, getState: () => IRootState) => Promise<void> {
	return async (dispatch, getState) => {
		try {
			const { currentSiteInfo, type } = getStateValues(getState);
			const id = currentSiteInfo?.id;

			const responseActions = {
				handleSuccess: (menus: IMenu[]) => {
					dispatch(setSavedMenus(menus));
					const current = type !== "" ? type : menus.length ? menus[0].name : "";
					dispatch(setCurrentType(current));
					setEditorMenuContent(menus, current, dispatch);
					updateTotalItems(dispatch, getState);
				},
				handleError: (response: any) => appActions.handleError(response)(dispatch),
			};

			const callback = async () => id && (await menus.getMenus(id));

			const loading = hasLoading ? [appActions.setIsLoading] : [];

			await handleRequest(callback, responseActions, loading)(dispatch);
		} catch (e) {
			console.log("Error", e);
		}
	};
}

function getCurrentMenu(menuType: string): (dispatch: Dispatch, getState: () => IRootState) => Promise<void> {
	return async (dispatch, getState) => {
		try {
			dispatch(setIsLoading(true));
			const { savedMenus } = getStateValues(getState);
			setEditorMenuContent(savedMenus || [], menuType, dispatch);
			updateTotalItems(dispatch, getState);
			dispatch(setIsLoading(false));
		} catch (e) {
			console.log("Error", e);
		}
	};
}

function addMenuItem(item: IMenuItem): (dispatch: Dispatch, getState: () => IRootState) => Promise<void> {
	return async (dispatch, getState) => {
		const { editorMenu } = getStateValues(getState);
		if (!editorMenu) return;
		const elements = editorMenu.elements || [];
		const itemWithTemporaryId = { ...item, id: new Date().getTime() };
		const menuElements = [...elements, itemWithTemporaryId];
		const updatedMenu = { ...editorMenu, elements: menuElements };

		dispatch(setEditorMenu(updatedMenu));
		updateTotalItems(dispatch, getState);
	};
}

function updateMenuItem(updatedItem: IMenuItem): (dispatch: Dispatch, getState: () => IRootState) => Promise<void> {
	return async (dispatch, getState) => {
		const { editorMenu } = getStateValues(getState);
		if (!editorMenu) return;

		const updateElements = (elements: IMenuItem[]): any => {
			const hasUpdatedChild = elements.some((element) => element.id === updatedItem.id);
			if (hasUpdatedChild) {
				const elementsWithUpdate = elements.map((item: IMenuItem) => {
					return item.id === updatedItem.id ? updatedItem : item;
				});
				return elementsWithUpdate;
			}
			for (const element of elements) {
				const updatedChildren = updateElements(element.children);
				if (updatedChildren) {
					element.children = updatedChildren;
					return elements;
				}
			}
		};

		const updatedElements = updateElements([...editorMenu.elements]);

		const updatedMenu = {
			...editorMenu,
			elements: updatedElements,
		};

		dispatch(setEditorMenu(updatedMenu));
	};
}

function deleteMenuItem(
	deletedItemId: number,
	deleteChildren = true,
): (dispatch: Dispatch, getState: () => IRootState) => Promise<void> {
	return async (dispatch, getState) => {
		const { editorMenu } = getStateValues(getState);
		if (!editorMenu) return;

		const updateElements = (elements: IMenuItem[]): IMenuItem[] | undefined => {
			const deletedItem = elements.find((element) => element.id === deletedItemId);
			if (deletedItem) {
				if (deleteChildren) {
					return elements.filter((item) => item.id !== deletedItemId);
				} else {
					const keptChildren = [...deletedItem.children];
					const elementsUpdated = [];
					for (const element of elements) {
						element.id === deletedItemId ? elementsUpdated.push(...keptChildren) : elementsUpdated.push(element);
					}
					return elementsUpdated;
				}
			} else {
				for (const element of elements) {
					const updatedChildren = updateElements(element.children);
					if (updatedChildren) {
						element.children = updatedChildren;
						return elements;
					}
				}
			}
		};

		const updatedElements = updateElements([...editorMenu.elements]) || [];

		const updatedMenu = {
			...editorMenu,
			elements: updatedElements,
		};

		dispatch(setEditorMenu(updatedMenu));
		updateTotalItems(dispatch, getState);
	};
}

function updateMenu(): (dispatch: Dispatch, getState: () => IRootState) => Promise<void> {
	return async (dispatch, getState) => {
		try {
			const { savedMenu, editorMenu } = getStateValues(getState);
			if (!savedMenu || !editorMenu) return;
			savedMenu.elements = editorMenu.elements;

			const responseActions = {
				handleSuccess: async () => {
					getMenus(false)(dispatch, getState);
				},
				handleError: (response: any) => appActions.handleError(response)(dispatch),
			};

			const callback = async () => menus.updateMenu(savedMenu, savedMenu.id);

			await handleRequest(callback, responseActions, [appActions.setIsSaving])(dispatch);
		} catch (e) {
			console.log("Error", e);
		}
	};
}

function reorderMenu(elements: IMenuItem[]): (dispatch: Dispatch, getState: () => IRootState) => Promise<void> {
	return async (dispatch, getState) => {
		const { editorMenu } = getStateValues(getState);
		if (!editorMenu) return;

		const updatedMenu = {
			...editorMenu,
			elements,
		};

		dispatch(setEditorMenu(updatedMenu));
	};
}

function setItemValue(page: IMenuItem): (dispatch: Dispatch) => Promise<void> {
	return async (dispatch) => {
		dispatch(setItem(page));
	};
}

function updateFormValue(valueObj: any): (dispatch: Dispatch, getState: () => IRootState) => void {
	return (dispatch, getState) => {
		const {
			menu: { form },
		} = getState();
		const updatedForm = { ...form, ...valueObj };

		dispatch(setMenuFormData(updatedForm));
	};
}

function resetItemValues(): (dispatch: Dispatch) => Promise<void> {
	return async (dispatch) => {
		dispatch(setMenuFormData(menuInitialState.form));
	};
}

const resetMenuValues = (dispatch: Dispatch) => {
	dispatch(setSavedMenus([]));
	dispatch(setEditorMenu(null));
	dispatch(setMenuFormData(menuInitialState.form));
};

const updateTotalItems = (dispatch: Dispatch, getState: () => IRootState) => {
	const { editorMenu } = getState().menu;
	if (!editorMenu) return;

	let counter = 0;
	const calculateItems = (elements: IMenuItem[]): void => {
		for (const element of elements) {
			counter++;
			calculateItems(element.children);
		}
	};
	if (editorMenu.elements) {
		calculateItems([...editorMenu.elements]);
	}

	dispatch(setTotalItems(counter));
};

export {
	setSavedMenus,
	setEditorMenu,
	setItem,
	setEditorMenuContent,
	getMenus,
	updateMenu,
	getCurrentMenu,
	addMenuItem,
	updateMenuItem,
	deleteMenuItem,
	setItemValue,
	resetItemValues,
	resetMenuValues,
	reorderMenu,
	updateFormValue,
	setCurrentType,
};
