<script lang="ts">
	import { onDestroy } from 'svelte';
	import Composite from './Composite.svelte';
	import components from '$lib/core/componentLoader';
	import services from '$lib/core/servicesLoader';
	import { dataStore } from '$lib/core/dataLoader';
	import { createCompositeStore, getCompositeStore } from '$lib/core/compositeStores';
	import { coreServices } from './coreServices';

	interface ICompositeLayout {
		areas: string;
		columns?: string;
		rows?: string;
		gap?: string;
		tailwindClasses?: string;
	}

	interface IComposite {
		layout?: ICompositeLayout;
		id: string;
		slot?: string;
		component: string;
		services?: string[];
		map?: Record<string, string>;
		store?: Record<string, any>;
		children?: IComposite[];
		servicesLoaded?: boolean;
	}

	export let composite: IComposite;
	let loadedServices: Record<string, any> = {
		core: coreServices
	};
	let layoutStyle = '';

	$: {
		layoutStyle = computeLayoutStyle(composite?.layout);

		initializeAndLoadServices(composite);
		initializeCompositeState(composite);
		mapAndSubscribe(composite);

		if (composite?.children) {
			composite.children.forEach((child) => {
				initializeCompositeState(child);
				initializeAndLoadServices(child);
				mapAndSubscribe(child);
			});
		}
	}

	function computeLayoutStyle(layout?: ICompositeLayout): string {
		if (!layout) return '';
		return `
            grid-template-areas: ${layout.areas};
            ${layout.gap ? `gap: ${layout.gap};` : ''}
            ${layout.columns ? `grid-template-columns: ${layout.columns};` : ''}
            ${layout.rows ? `grid-template-rows: ${layout.rows};` : ''}
        `;
	}

	function initializeAndLoadServices(component: IComposite) {
		if (!component) return;

		if (component.services) {
			let servicePromises = component.services.map((serviceName) =>
				loadedServices[serviceName]
					? Promise.resolve(loadedServices[serviceName])
					: loadService(serviceName)
			);

			Promise.all(servicePromises).then((loaded) => {
				loaded.forEach((service, index) => {
					loadedServices[component.services[index]] = service;
				});
				component.servicesLoaded = true;
			});
		} else {
			component.servicesLoaded = true;
		}
	}

	function initializeCompositeState(child: IComposite) {
		if (child.id) {
			child.store = createCompositeStore(child.id, child.store || {});
		}

		if (child.children) {
			child.children.forEach(initializeCompositeState);
		}
	}

	let unsubscribers = [];

	function mapAndSubscribe(component: IComposite) {
		if (component.map) {
			const localStore = getCompositeStore(component.id);

			for (const [localKey, mapping] of Object.entries(component.map)) {
				const isDataMapping = mapping.startsWith('@data:');
				const isStoreMapping = mapping.startsWith('@');

				if (isDataMapping) {
					const externalKey = mapping.replace('@data:', '').trim();

					const unsubscribe = dataStore.subscribe((store) => {
						if (externalKey in store) {
							if (store[externalKey] && typeof store[externalKey].subscribe === 'function') {
								let innerUnsub = store[externalKey].subscribe((value) => {
									localStore.update((storeValue) => ({ ...storeValue, [localKey]: value }));
								});
								unsubscribers.push(innerUnsub);
							} else {
								localStore.update((storeValue) => ({
									...storeValue,
									[localKey]: store[externalKey]
								}));
							}
						}
					});

					unsubscribers.push(unsubscribe);
				} else if (isStoreMapping) {
					const [externalID, externalKey] = mapping
						.replace('@', '')
						.split(':')
						.map((item) => item.trim());

					const externalStore = getCompositeStore(externalID);
					if (externalStore) {
						const unsubscribe = externalStore.subscribe((externalState) => {
							if (externalState && externalKey in externalState) {
								localStore.update((storeValue) => ({
									...storeValue,
									[localKey]: externalState[externalKey]
								}));
							}
						});

						unsubscribers.push(unsubscribe);
					}
				}
			}
		}

		if (component.children) {
			component.children.forEach(mapAndSubscribe);
		}
	}

	onDestroy(() => {
		unsubscribers.forEach((unsub) => unsub());
	});

	async function getComponent(componentName: string) {
		if (components[componentName]) {
			const module = await components[componentName]();
			return module.default;
		}
		return null;
	}

	async function loadService(serviceName: string) {
		if (services[serviceName]) {
			const module = await services[serviceName]();
			return module.default || module;
		}
		return null;
	}

	async function loadComponentAndService(component: IComposite) {
		return await getComponent(component.component);
	}
</script>

<div
	class={`grid w-full h-full overflow-hidden ${composite?.layout?.tailwindClasses || ''}`}
	style={layoutStyle}
>
	{#if composite?.servicesLoaded}
		{#await loadComponentAndService(composite) then Component}
			{#if Component}
				<svelte:component
					this={Component}
					id={composite.id}
					store={getCompositeStore(composite.id)}
					services={loadedServices}
				/>
			{/if}
		{/await}
	{/if}

	{#if composite?.children}
		{#each composite.children as child (child.id)}
			<div
				class="grid w-full h-full overflow-hidden ${composite?.layout?.tailwindClasses || ''}"
				style={`grid-area: ${child.slot}`}
			>
				{#if child.servicesLoaded}
					{#await loadComponentAndService(child) then ChildComponent}
						{#if ChildComponent}
							<svelte:component
								this={ChildComponent}
								id={child.id}
								store={getCompositeStore(child.id)}
								services={loadedServices}
							/>
							{#if child.children && child.children.length}
								<Composite composite={child} />
							{/if}
						{:else}
							<p>Component {child.component} not found.</p>
						{/if}
					{/await}
				{:else}
					<p>Loading services for child {child.id}...</p>
				{/if}
			</div>
		{/each}
	{/if}
</div>
