import type {
	BuilderModule,
	ComponentSettings,
	ComponentSettingsToProps,
	PropArray,
} from '@nitrogenbuilder/types';

import { nitrogen } from '../index.js';

import { getDefault } from './getDefault.js';
import { renderTemplate } from './renderTemplate.js';
import { resolve } from './resolve.js';
import { set } from './set.js';

type UpdateWithDefaultPropsInput = {
	page: BuilderModule[];
	dynamicData?: any;
	requestedData?: any;
};

type ProcessedModule = {
	id: string;
	module: string | { name: string };
	props: Record<string, any>;
	children?: ProcessedModule[] | Record<string, ProcessedModule[]>;
	requestedData?: any;
};

function processChildren(
	mod: BuilderModule,
	dynamicData: any = {},
	requestedData: any = {}
): ProcessedModule[] | Record<string, ProcessedModule[]> | undefined {
	if (!mod.props.children) return undefined;

	if (Array.isArray(mod.props.children)) {
		return mod.props.children.map((child: any) => {
			return processModule(child, dynamicData, requestedData);
		});
	} else if (typeof mod.props.children === 'object') {
		return Object.keys(mod.props.children).reduce((acc, slot) => {
			if (Array.isArray(mod.props.children[slot])) {
				return {
					...acc,
					[slot]: mod.props.children[slot].map((child: any) => {
						return processModule(child, dynamicData, requestedData);
					}),
				};
			}
			return acc;
		}, {});
	}

	return undefined;
}

type DeepPartial<T> = T extends object
	? {
			[P in keyof T]?: DeepPartial<T[P]>;
		}
	: T;

export function renderModuleProps<T extends ComponentSettings | string>(
	props: T extends ComponentSettings
		? DeepPartial<ComponentSettingsToProps<T>>
		: Record<string, any>,
	settings: T,
	dynamicData: any = {},
	requestedData: any = {}
): T extends ComponentSettings ? ComponentSettingsToProps<T> : any {
	let _settings: ComponentSettings;

	if (typeof settings === 'string') {
		const module = nitrogen.getModule(settings);
		if (!module) return {} as any;
		_settings = {
			categories: module.categories,
			options: module.options,
		} as ComponentSettings;
	} else {
		_settings = settings;
	}

	let spreadProps = { ...(props ?? {}) } as any;
	delete spreadProps.children;

	function recursiveArrayPropDefault(prop: PropArray, keys: string[]) {
		if (prop.props) {
			Object.entries(prop.props).forEach(([subPropK, subPropV]) => {
				if (subPropV.type === 'array') {
					let indexes = Object.keys(resolve(keys.join('.'), spreadProps) ?? {});

					for (let i = 0; i < indexes.length; i++) {
						recursiveArrayPropDefault(subPropV as PropArray, [
							...keys,
							`${indexes[i]}`,
							subPropK,
						]);
					}
				} else {
					let indexes = Object.keys(resolve(keys.join('.'), spreadProps) ?? {});

					for (let i = 0; i < indexes.length; i++) {
						set(
							spreadProps,
							getDefault(
								resolve(
									[...keys, `${indexes[i]}`, subPropK].join('.'),
									spreadProps
								),
								subPropV
							),
							[...keys, `${indexes[i]}`, subPropK]
						);
					}
				}
			});
		}
	}

	Object.values(_settings.categories).forEach((category) => {
		Object.entries(category.groups).forEach(([groupK, group]) => {
			Object.entries(group.props).forEach(([propK, propV]) => {
				if (spreadProps[groupK] === undefined) {
					spreadProps[groupK] = {};
				}
				if (propV.type === 'array') {
					recursiveArrayPropDefault(propV as PropArray, [groupK, propK]);
				} else {
					spreadProps[groupK][propK] = getDefault(
						spreadProps[groupK][propK],
						propV
					);
				}
			});
		});
	});

	function recursiveDynamicProps(keys: string[]) {
		let props = resolve(keys.join('.'), spreadProps);

		if (!props) return;
		let propChildrenKeys = Object.keys(props);

		for (let j = 0; j < propChildrenKeys.length; j++) {
			let propChildrenKey = propChildrenKeys[j];
			let propChild = props[propChildrenKey];

			if (propChildrenKey.endsWith('___dynamic')) {
				const dynamicVal =
					resolve(propChild.key, dynamicData) || propChild.fallback || '';

				props[propChildrenKey.replace('___dynamic', '')] =
					typeof dynamicVal === 'boolean'
						? dynamicVal
						: (propChild.before || '') + dynamicVal + (propChild.after || '');
			} else if (typeof props[propChildrenKey] === 'object') {
				recursiveDynamicProps([...keys, propChildrenKey]);
			} else if (typeof props[propChildrenKey] === 'string') {
				props[propChildrenKey] = renderTemplate(
					props[propChildrenKey],
					dynamicData
				);
			}
		}
	}

	let groupKeys = Object.keys(spreadProps);
	for (let i = 0; i < groupKeys.length; i++) {
		let groupKey = groupKeys[i];

		if (typeof groupKey === 'string') {
			if (typeof spreadProps[groupKey] === 'object') {
				// check children properties for if the end in ___dynamic
				recursiveDynamicProps([groupKey]);
			}
		}
	}

	return spreadProps as any;
}

function processModule(
	mod: BuilderModule,
	dynamicData: any = {},
	requestedData: any = {},
	includeChildren: boolean = true
): ProcessedModule {
	const module =
		typeof mod.module === 'object'
			? mod.module
			: nitrogen.getModule(mod.module);

	let spreadProps = { ...(mod?.props ?? {}) };
	delete spreadProps.children;

	if (typeof module === 'object') {
		function recursiveArrayPropDefault(prop: PropArray, keys: string[]) {
			if (prop.props) {
				Object.entries(prop.props).forEach(([subPropK, subPropV]) => {
					if (subPropV.type === 'array') {
						let indexes = Object.keys(
							resolve(keys.join('.'), spreadProps) ?? {}
						);

						for (let i = 0; i < indexes.length; i++) {
							recursiveArrayPropDefault(subPropV as PropArray, [
								...keys,
								`${indexes[i]}`,
								subPropK,
							]);
						}
					} else {
						let indexes = Object.keys(
							resolve(keys.join('.'), spreadProps) ?? {}
						);

						for (let i = 0; i < indexes.length; i++) {
							set(
								spreadProps,
								getDefault(
									resolve(
										[...keys, `${indexes[i]}`, subPropK].join('.'),
										spreadProps
									),
									subPropV
								),
								[...keys, `${indexes[i]}`, subPropK]
							);
						}
					}
				});
			}
		}

		Object.values(module.categories).forEach((category) => {
			Object.entries(category.groups).forEach(([groupK, group]) => {
				Object.entries(group.props).forEach(([propK, propV]) => {
					if (spreadProps[groupK] === undefined) {
						spreadProps[groupK] = {};
					}
					if (propV.type === 'array') {
						recursiveArrayPropDefault(propV as PropArray, [groupK, propK]);
					} else {
						spreadProps[groupK][propK] = getDefault(
							spreadProps[groupK][propK],
							propV
						);
					}
				});
			});
		});
	}

	function recursiveDynamicProps(keys: string[]) {
		let props = resolve(keys.join('.'), spreadProps);

		if (!props) return;
		let propChildrenKeys = Object.keys(props);

		for (let j = 0; j < propChildrenKeys.length; j++) {
			let propChildrenKey = propChildrenKeys[j];
			let propChild = props[propChildrenKey];

			if (propChildrenKey.endsWith('___dynamic')) {
				const dynamicVal =
					resolve(propChild.key, dynamicData) || propChild.fallback || '';

				props[propChildrenKey.replace('___dynamic', '')] =
					typeof dynamicVal === 'boolean'
						? dynamicVal
						: (propChild.before || '') + dynamicVal + (propChild.after || '');
			} else if (typeof props[propChildrenKey] === 'object') {
				recursiveDynamicProps([...keys, propChildrenKey]);
			} else if (typeof props[propChildrenKey] === 'string') {
				props[propChildrenKey] = renderTemplate(
					props[propChildrenKey],
					dynamicData
				);
			}
		}
	}

	let groupKeys = Object.keys(spreadProps);
	for (let i = 0; i < groupKeys.length; i++) {
		let groupKey = groupKeys[i];

		if (typeof groupKey === 'string') {
			if (typeof spreadProps[groupKey] === 'object') {
				// check children properties for if the end in ___dynamic
				recursiveDynamicProps([groupKey]);
			}
		}
	}

	const children = includeChildren
		? processChildren(mod, dynamicData, requestedData)
		: undefined;

	return {
		id: mod.id,
		module: mod.module,
		props: spreadProps,
		...(children && { children }),
		...(requestedData[mod.id] && { requestedData: requestedData[mod.id] }),
	};
}

export function updateWithDefaultProps({
	page,
	dynamicData,
	requestedData,
}: UpdateWithDefaultPropsInput): ProcessedModule[] {
	return page.map((block: BuilderModule) => {
		return processModule(block, dynamicData, requestedData);
	});
}

export function updateModuleProps(
	module: BuilderModule,
	dynamicData: any = {},
	requestedData: any = {},
	includeChildren: boolean = false
): ProcessedModule {
	return processModule(module, dynamicData, requestedData, includeChildren);
}
