import { deepClone, getDisplayName, getSchema, getTemplate } from "@ax/helpers";
import type { IBreadcrumbItem, IPage } from "@ax/types";

const configKeys = ["headerConfig", "footerConfig"];

const parseData = (data: any, fromApi: boolean) => {
	const defensiveCopy = deepClone(data);

	const setKey = fromApi ? JSON.parse : JSON.stringify;

	configKeys.forEach((key: string) => {
		if (data[key]) {
			const meetsCondition = (key: any) => (fromApi ? typeof key === "string" : typeof key === "object");
			defensiveCopy[key] = meetsCondition(data[key]) && setKey(data[key]);
		}
	});

	return defensiveCopy;
};

const cleanContent = (originalPageContent: IPage) => {
	const pageContent = deepClone(originalPageContent);
	return parseData(pageContent, false);
};

const findByEditorID = (content: any, editorID: number): { element: any; parent: any; grandParent: any } => {
	const queue: any[] = [{ element: content, parent: null, grandParent: null }];
	while (queue.length > 0) {
		const obj = queue.shift();
		const currentObj = obj.element;

		if (currentObj.editorID === editorID) {
			return obj;
		}

		const keys = currentObj instanceof Object ? Object.keys(currentObj) : [];

		let parent = currentObj;
		if (editorID === 0) {
			parent = null;
		}

		for (const key of keys) {
			const objVal = currentObj[key];
			if (objVal instanceof Object) {
				queue.push({ element: objVal, parent, grandParent: obj.parent });
			}
		}
	}
	return { element: null, parent: null, grandParent: null };
};

const generateEditorIDs = (originalPageContent: any) => {
	let lastGeneratedID = 0;

	const setEditorID = (component: any, parentID: number | null) => {
		if (!component || typeof component !== "object") return component;

		const isTemplate = "type" in component && (component.type === "template" || component.type === "formTemplate");

		if ("component" in component || isTemplate) {
			component.editorID = lastGeneratedID++;
			component.parentEditorID = parentID;
		}

		const nextParentID = Object.hasOwn(component, "editorID") ? component.editorID : parentID;

		for (const key in component) {
			component[key] = setEditorID(component[key], nextParentID);
		}

		return component;
	};

	let pageContent = (originalPageContent && deepClone(originalPageContent)) || {};
	pageContent = setEditorID(pageContent, null);

	return { pageContent };
};

const filterBreadcrumb = (breadcrumb: IBreadcrumbItem[]) => {
	return breadcrumb.filter((item: IBreadcrumbItem) => {
		return !item.component.toLowerCase().includes("section");
	});
};

const getNewBreadcrumb = (componentsTree: any, editorID: number, isFiltered?: boolean) => {
	let newBreadcrumb: IBreadcrumbItem[] = [];

	const leaf = findByEditorID(componentsTree, editorID);

	if (leaf.element.component) {
		newBreadcrumb = [
			{
				editorID: leaf.element.editorID,
				displayName: getDisplayName(leaf.element.component) || leaf.element.component,
				component: leaf.element.component,
			},
			...newBreadcrumb,
		];
	}
	if (leaf.element.parentEditorID !== null) {
		newBreadcrumb = [...getNewBreadcrumb(componentsTree, leaf.element.parentEditorID, isFiltered), ...newBreadcrumb];
	}

	newBreadcrumb = isFiltered ? filterBreadcrumb(newBreadcrumb) : newBreadcrumb;
	return newBreadcrumb;
};

const cleanPageValues = (updatedValues: any, originalValues: any) => {
	delete updatedValues["modified"];
	delete updatedValues["entity"];
	delete updatedValues["structuredDataContent"];
	delete updatedValues["headerConfig"];
	delete updatedValues["footerConfig"];
	delete updatedValues["liveStatus"];
	delete updatedValues["draft"];
	delete updatedValues["hash"];
	delete updatedValues["editing"];
	delete updatedValues["pageLanguages"];
	delete updatedValues["availableSites"];
	delete updatedValues["fullUrl"];

	delete originalValues["modified"];
	delete originalValues["entity"];
	delete originalValues["structuredDataContent"];
	delete originalValues["headerConfig"];
	delete originalValues["footerConfig"];
	delete originalValues["liveStatus"];
	delete originalValues["draft"];
	delete originalValues["hash"];
	delete originalValues["editing"];
	delete originalValues["pageLanguages"];
	delete originalValues["availableSites"];
	delete originalValues["fullUrl"];

	return {
		cleanUpdatedValues: updatedValues,
		cleanOriginalValues: originalValues,
	};
};

const getLastModuleEditorID = (sections: any, sectionIndex: number) => {
	const updatedModules = sections[sectionIndex].modules;
	const lastModuleIndex = updatedModules.length - 1;
	return updatedModules[lastModuleIndex].editorID;
};

const getLastComponentEditorID = (sections: any, parentEditorID: number, key: string): number | null => {
	const { element: module } = findByEditorID(sections, parentEditorID);
	if (!module || module[key] === undefined) return null;
	const lastElementIndex = module[key].length - 1;
	return module[key][lastElementIndex].editorID;
};

const getParentKey = (parentModule: any, editorID: number) => {
	let keyFound = "";
	Object.keys(parentModule).forEach((objKey: any) => {
		if (Array.isArray(parentModule[objKey])) {
			const moduleFound = parentModule[objKey].find((module: any) => module.editorID === editorID);
			if (moduleFound) {
				keyFound = objKey;
			}
		}
	});
	return keyFound;
};

const checkMaxModules = (content: any, type: string): { isMaxModules: boolean; errorMessage?: string } => {
	const schema = getSchema(type);
	const { maxModulesPerPage } = schema || {};
	const queue: any[] = [content];
	let counter = 0;

	if (maxModulesPerPage === undefined) return { isMaxModules: false };

	while (queue.length > 0 && counter < maxModulesPerPage) {
		const obj = queue.shift();
		const currentObj = obj;

		if (currentObj.component === type) {
			counter++;
		}

		const keys = currentObj instanceof Object ? Object.keys(currentObj) : [];

		for (const key of keys) {
			const objVal = currentObj[key];
			if (objVal instanceof Object) {
				queue.push(objVal);
			}
		}
	}

	const isMaxModules = counter >= maxModulesPerPage;
	const maxModulesText = maxModulesPerPage === 1 ? "one" : maxModulesPerPage;
	const errorMessage = `There can be only ${maxModulesText} ${type} on page. You already have it.`;

	return {
		isMaxModules,
		...(isMaxModules && { errorMessage }),
	};
};

const evaluateComputedFields = async (page: IPage, operation: "save" | "refresh") => {
	const updatedPage = deepClone(page);
	const pageSchemaContent = getTemplate(page.template.templateType).content;
	const data = {
		operation,
		apiUrl: process.env.GRIDDO_API_URL || process.env.REACT_APP_API_ENDPOINT,
		publicApiUrl: process.env.GRIDDO_PUBLIC_API_URL || process.env.REACT_APP_PUBLIC_API_ENDPOINT,
	};
	for (const field of pageSchemaContent || []) {
		if (Object.prototype.hasOwnProperty.call(field, "computed")) {
			// biome-ignore lint/security/noGlobalEval: we need to evaluate the computed field
			const computedFunction = eval(`(${field.computed})`);
			updatedPage.template[field.key] = await computedFunction(page, data);
		}
	}

	return updatedPage;
};

function findByComponent(obj: any, componentName: string) {
	if (typeof obj !== "object" || obj === null) return null;

	if (obj.component === componentName) return obj;

	for (const key in obj) {
		if (typeof obj[key] === "object") {
			const found: any = findByComponent(obj[key], componentName);
			if (found) return found;
		}
	}

	return null;
}

export {
	checkMaxModules,
	cleanContent,
	cleanPageValues,
	evaluateComputedFields,
	filterBreadcrumb,
	findByComponent,
	findByEditorID,
	generateEditorIDs,
	getLastComponentEditorID,
	getLastModuleEditorID,
	getNewBreadcrumb,
	getParentKey,
	parseData,
};
