import { nitrogen } from '@nitrogenbuilder/client-core';
import type {
	BuilderModule,
	Category,
	ComponentSettings,
	ComponentSettingsToProps,
	Group,
	Prop,
	PropArray,
} from '@nitrogenbuilder/types';

import NitrogenContentSlot, {
	nitrogenContentSlotSettings,
} from './NitrogenContentSlot.js';
import { getDefault } from './utils/getDefault.js';
import { renderTemplate } from './utils/renderTemplate.js';
import { resolve } from './utils/resolve.js';
import { set } from './utils/set.js';

const isBuilderMode =
	typeof window !== 'undefined' &&
	new URLSearchParams(window.location.search).has('nitrogen-builder');

// Auto-register built-in content slot module
nitrogen.registerModule(
	'Nitrogen Content Slot',
	NitrogenContentSlot,
	nitrogenContentSlotSettings
);

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

function renderChildren(
	mod: BuilderModule,
	dynamicData: any = {},
	requestedData: any = {},
	contentPage?: BuilderModule[],
	contentSlotRef?: { filled: boolean }
) {
	if (!mod.props.children) return undefined;

	if (Array.isArray(mod.props.children)) {
		return mod.props.children.flatMap((child: any) => {
			return renderModule(
				child,
				dynamicData,
				requestedData,
				contentPage,
				contentSlotRef
			);
		});
	} 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].flatMap((child: any) => {
						return renderModule(
							child,
							dynamicData,
							requestedData,
							contentPage,
							contentSlotRef
						);
					}),
				};
			}
			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 || '';

				const displayVal =
					typeof dynamicVal === 'boolean'
						? dynamicVal
						: (propChild.before || '') +
							(isBuilderMode && typeof dynamicVal === 'string'
								? `{{ ${dynamicVal} }}`
								: dynamicVal) +
							(propChild.after || '');

				props[propChildrenKey.replace('___dynamic', '')] = displayVal;
			} else if (typeof props[propChildrenKey] === 'object') {
				recursiveDynamicProps([...keys, propChildrenKey]);
			} else {
				props[propChildrenKey] = renderTemplate(
					props[propChildrenKey],
					dynamicData,
					isBuilderMode
				);
			}
		}
	}

	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 renderModule(
	mod: BuilderModule,
	dynamicData: any = {},
	requestedData: any = {},
	contentPage?: BuilderModule[],
	contentSlotRef?: { filled: boolean }
) {
	const moduleName =
		typeof mod.module === 'object' ? mod.module.name : mod.module;

	// Intercept content slot: replace with content modules when contentPage is provided
	if (
		moduleName === 'Nitrogen Content Slot' &&
		contentPage &&
		contentSlotRef &&
		!contentSlotRef.filled
	) {
		contentSlotRef.filled = true;
		return (
			<div
				key={mod.id}
				data-nitrogen-location='template-content'
			>
				{contentPage.map((block) =>
					renderModule(block, dynamicData, requestedData)
				)}
			</div>
		);
	}

	const Component = nitrogen.getModuleComponent(moduleName);
	if (!Component) {
		console.groupCollapsed('NitrogenRenderer/renderModule/Component');
		console.warn(
			'Component not found for module. Possibly removed or renamed.',
			mod
		);
		console.groupEnd();

		return null;
	}
	const module =
		typeof mod.module === 'object'
			? mod.module
			: nitrogen.getModule(mod.module);

	const hasSlots =
		typeof mod.props?.children === 'object' &&
		!Array.isArray(mod.props?.children);

	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: Category) => {
			Object.entries(category.groups).forEach(([groupK, group]: [string, Group]) => {
				Object.entries(group.props).forEach(([propK, propV]: [string, Prop]) => {
					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 || '';

				const displayVal =
					typeof dynamicVal === 'boolean'
						? dynamicVal
						: (propChild.before || '') +
							(isBuilderMode && typeof dynamicVal === 'string'
								? `{{ ${dynamicVal} }}`
								: dynamicVal) +
							(propChild.after || '');

				props[propChildrenKey.replace('___dynamic', '')] = displayVal;
			} else if (typeof props[propChildrenKey] === 'object') {
				recursiveDynamicProps([...keys, propChildrenKey]);
			} else {
				props[propChildrenKey] = renderTemplate(
					props[propChildrenKey],
					dynamicData,
					isBuilderMode
				);
			}
		}
	}

	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 (
		<Component
			key={mod.id}
			id={mod.id}
			{...spreadProps}
			requestedData={requestedData[mod.id] ?? undefined}
			{...(hasSlots && {
				slots: renderChildren(
					mod,
					dynamicData,
					requestedData,
					contentPage,
					contentSlotRef
				),
			})}
		>
			{hasSlots
				? null
				: renderChildren(
						mod,
						dynamicData,
						requestedData,
						contentPage,
						contentSlotRef
					)}
		</Component>
	);
}

export default function NitrogenRenderer({
	page,
	dynamicData,
	requestedData,
	contentPage,
}: NitrogenRendererProps) {
	const contentSlotRef = contentPage ? { filled: false } : undefined;
	console.log('NitrogenRenderer page', page, contentSlotRef);
	return (
		<>
			{page.map((block: BuilderModule) => {
				return renderModule(
					block,
					dynamicData,
					requestedData,
					contentPage,
					contentSlotRef
				);
			})}
		</>
	);
}
