import { getDefaultSchema, camelize, isSelectedEditorID, deepClone } from "@ax/helpers";
import { findByEditorID } from "./editor";
import type { IComponent, IModule } from "@ax/types";

const updateElementCollection = (elementType: string, prevCollection: any[]) => {
	const newElement = getDefaultSchema(elementType);
	return prevCollection ? [...prevCollection, newElement] : [newElement];
};

const addElement = (componentType: IComponent) => {
	const defaultSchema = getDefaultSchema(componentType.component);
	for (const key in defaultSchema) {
		componentType[key] = defaultSchema[key];
	}
	return componentType;
};

const updateComponent = (component: any, schema: any) => {
	for (const key in schema) {
		if (!(key in component)) {
			const defaultVal = schema[key] && typeof schema[key] === "object" ? { ...schema[key] } : schema[key];
			component[key] = defaultVal;
		}
	}
};

const updateCollection = (type: string, item: IModule, key: string) => {
	item[key] = updateElementCollection(type, item[key]);
};

const getUpdatedComponents = (
	sections: any[],
	component: { editorID: number; type: IComponent | string },
	key: string,
) => {
	const { editorID, type } = component;

	const isCollectionItem = typeof type === "string";
	let selectedIndex = 0;

	const addItem = (item: IModule) => (isCollectionItem ? updateCollection(type, item, key) : addElement(type));

	const sectionEntries = sections.map((section: any, index: number) => {
		const updatedModules = section.modules.map((module: IModule) => {
			const mapValues = (item: IModule) => {
				if (item.editorID && item.editorID === editorID) {
					addItem(item);
					selectedIndex = index;
				}
				const itemKeys = Object.keys(item);
				itemKeys.forEach((key: string) => {
					const containedValue = item[key];
					if (containedValue && typeof containedValue === "object") {
						mapValues(containedValue);
					}
				});
			};
			mapValues(module);
			return module;
		});

		const sectionKey = camelize(section.name);
		section.modules = [...updatedModules];

		return [sectionKey, section];
	});
	const updatedSections = Object.fromEntries(sectionEntries);

	return {
		updatedSections,
		selectedIndex,
	};
};

const getUpdatedSections = (sections: any[], id: number, moduleType: string) => {
	let selectedIndex = 0;
	const sectionEntries = sections.map((section: any, index: number) => {
		const { modules, key } = section;
		if (isSelectedEditorID(section, id)) {
			selectedIndex = index;
			const newModule = getDefaultSchema(moduleType);
			const updatedModules = [...modules, newModule];
			section.modules = [...updatedModules];
		}

		return [key, section];
	});
	const updatedSections = Object.fromEntries(sectionEntries);

	return {
		updatedSections,
		selectedIndex,
	};
};

const deleteComponent = (elements: any, editorID: number) => {
	const isArray = elements.length || elements.length === 0;
	if (isArray) {
		elements = elements.filter((element: any) => {
			if (element.elements) {
				element.elements = deleteComponent(element.elements, editorID);
			}
			return !isSelectedEditorID(element, editorID);
		});
	} else {
		Object.keys(elements).forEach((objKey: any) => {
			const element = elements[objKey];
			if (isSelectedEditorID(element, editorID)) {
				delete elements[objKey];
			} else if (element.elements) {
				element.elements = element.elements.filter((el: any) => !isSelectedEditorID(el, editorID));
			}
		});
	}

	return elements;
};

const setKeyValue = (obj: any, key: string, val: any) => {
	const [parentKey, childKey] = key.split(".");
	if (childKey) {
		if (obj[parentKey] && typeof obj[parentKey] === "object") {
			obj[parentKey][childKey] = val;
		}
	} else {
		obj[parentKey] = val;
	}
};

const updateByEditorID = (content: any, editorID: number, contentKey: string | string[], value: any): any => {
	if (!content || typeof content !== "object") return content;

	if (content.editorID === editorID) {
		if (Array.isArray(contentKey)) {
			const keys = contentKey;
			const values = Array.isArray(value) ? value : [value];
			keys.forEach((k, i) => {
				setKeyValue(content, k, values[i]);
			});
		} else {
			setKeyValue(content, contentKey, value);
		}

		return content;
	}

	for (const key in content) {
		if (Object.prototype.hasOwnProperty.call(content, key)) {
			const child = content[key];
			if (typeof child === "object") {
				content[key] = updateByEditorID(child, editorID, contentKey, value);
			}
		}
	}

	return content;
};

const moveElement = (elementID: number[], arr: any[], newIndex: number, idKey = "editorID"): any[] => {
	const arrCopy = deepClone(arr);
	if (arrCopy.length <= 1) return arrCopy;
	elementID.forEach((id) => {
		const elementIndex = arrCopy.findIndex((el: any) => el[idKey] === id);
		const element = { ...arrCopy[elementIndex] };
		arrCopy.splice(elementIndex, 1);
		arrCopy.splice(newIndex, 0, element);
	});
	return arrCopy;
};

const moveModule = (params: IMoveElementParams) => {
	const { elementID, content, selectedContent, newIndex, page, key } = params;
	const isPage = ["Page", "GlobalPage"].includes(selectedContent.component);
	let newContent: any;
	if (isPage) {
		const newModules = moveElement(elementID, content[key], newIndex);
		const { template } = selectedContent;
		const selectedSection = Object.keys(template).find(
			(key: string) => template[key] && template[key].editorID === content.editorID,
		);
		newContent = selectedSection
			? {
					...selectedContent,
					template: {
						...selectedContent.template,
						[selectedSection]: {
							...deepClone(content),
							modules: newModules,
						},
					},
				}
			: {
					...selectedContent,
					template: {
						...selectedContent.template,
						[key]: newModules,
					},
				};
	} else {
		const contentElements = content[key];
		const { template } = page;
		const { element: selectedModule } = findByEditorID(page, selectedContent.editorID);
		selectedModule[key] = moveElement(elementID, contentElements, newIndex);

		newContent = {
			...page,
			template: {
				...template,
			},
		};
	}
	return newContent;
};

function replaceElements(elements: any, elemType: string): any {
	const defaultSchema = getDefaultSchema(elemType);
	return elements.map((ele: any) => {
		let newElement = {};
		Object.keys(defaultSchema).forEach((key: string) => {
			const value = ele[key] ? ele[key] : defaultSchema[key];
			newElement = { ...newElement, [key]: value };
		});
		return { ...newElement, component: elemType };
	});
}

interface IMoveElementParams {
	elementID: number[];
	content: any;
	newIndex: number;
	selectedContent: any;
	page: any;
	key: string;
}

export {
	getUpdatedComponents,
	getUpdatedSections,
	updateComponent,
	updateByEditorID,
	moveModule,
	moveElement,
	deleteComponent,
	replaceElements,
	updateElementCollection,
	addElement,
};
