import { dedent, isSvelte5Plus, write_if_changed } from './utils.js';

/**
 * @param {import('types').ManifestData} manifest_data
 * @param {import('types').ValidatedConfig} config
 * @param {string} output
 */
export function write_root(manifest_data, config, output) {
	// TODO remove default layout altogether

	const use_boundaries = config.kit.experimental.handleRenderingErrors && isSvelte5Plus();

	const max_depth = Math.max(
		...manifest_data.routes.map((route) =>
			route.page ? route.page.layouts.filter(Boolean).length + 1 : 0
		),
		1
	);

	const levels = [];
	for (let i = 0; i <= max_depth; i += 1) {
		levels.push(i);
	}

	let l = max_depth;
	/** @type {string} */
	let pyramid;

	if (isSvelte5Plus() && use_boundaries) {
		// with the @const we force the data[depth] access to be derived, which is important to not fire updates needlessly
		// TODO in Svelte 5 we should rethink the client.js side, we can likely make data a $state and only update indexes that changed there, simplifying this a lot
		pyramid = dedent`
			{#snippet pyramid(depth)}
				{@const Pyramid = constructors[depth]}
				{#snippet failed(error)}
					{@const ErrorPage = errors[depth]}
					<ErrorPage {error} />
				{/snippet}
				<svelte:boundary failed={errors[depth] ? failed : undefined}>
					{#if constructors[depth + 1]}
						{@const d = data[depth]}
						<!-- svelte-ignore binding_property_non_reactive -->
						<Pyramid bind:this={components[depth]} data={d} {form} params={page.params}>
							{@render pyramid(depth + 1)}
						</Pyramid>
					{:else}
						{@const d = data[depth]}
						<!-- svelte-ignore binding_property_non_reactive -->
						<Pyramid bind:this={components[depth]} data={d} {form} params={page.params} {error} />
					{/if}
				</svelte:boundary>
			{/snippet}

			{@render pyramid(0)}
		`;
	} else {
		pyramid = dedent`
	${
		isSvelte5Plus()
			? `<!-- svelte-ignore binding_property_non_reactive -->
		<Pyramid_${l} bind:this={components[${l}]} data={data_${l}} {form} params={page.params} />`
			: `<svelte:component this={constructors[${l}]} bind:this={components[${l}]} data={data_${l}} {form} params={page.params} />`
	}`;

		while (l--) {
			pyramid = dedent`
			{#if constructors[${l + 1}]}
				${
					isSvelte5Plus()
						? dedent`{@const Pyramid_${l} = constructors[${l}]}
						<!-- svelte-ignore binding_property_non_reactive -->
						<Pyramid_${l} bind:this={components[${l}]} data={data_${l}} {form} params={page.params}>
							${pyramid}
						</Pyramid_${l}>`
						: dedent`<svelte:component this={constructors[${l}]} bind:this={components[${l}]} data={data_${l}} params={page.params}>
					${pyramid}
				</svelte:component>`
				}

			{:else}
				${
					isSvelte5Plus()
						? dedent`
					{@const Pyramid_${l} = constructors[${l}]}
					<!-- svelte-ignore binding_property_non_reactive -->
					<Pyramid_${l} bind:this={components[${l}]} data={data_${l}} {form} params={page.params} />
					`
						: dedent`<svelte:component this={constructors[${l}]} bind:this={components[${l}]} data={data_${l}} {form} params={page.params} />`
				}

			{/if}
		`;
		}
	}

	write_if_changed(
		`${output}/root.svelte`,
		dedent`
			<!-- This file is generated by @sveltejs/kit — do not edit it! -->
			${isSvelte5Plus() ? '<svelte:options runes={true} />' : ''}
			<script>
				import { setContext, ${isSvelte5Plus() ? '' : 'afterUpdate, '}onMount, tick } from 'svelte';
				import { browser } from '$app/env';

				// stores
				${
					isSvelte5Plus()
						? dedent`
							let { stores, page, constructors, components = [], form, ${use_boundaries ? 'errors = [], error, ' : ''}${levels
								.map((l) => `data_${l} = null`)
								.join(', ')} } = $props();
							${use_boundaries ? `let data = $derived({${levels.map((l) => `'${l}': data_${l}`).join(', ')}})` : ''}
						`
						: dedent`
							export let stores;
							export let page;

							export let constructors;
							export let components = [];
							export let form;
							${levels.map((l) => `export let data_${l} = null;`).join('\n')}
						`
				}

				if (!browser) {
					// svelte-ignore state_referenced_locally
					setContext('__svelte__', stores);
				}

				${
					isSvelte5Plus()
						? dedent`
							if (browser) {
								$effect.pre(() => stores.page.set(page));
							} else {
								// svelte-ignore state_referenced_locally
								stores.page.set(page);
							}
						`
						: '$: stores.page.set(page);'
				}
				${
					isSvelte5Plus()
						? dedent`
							$effect(() => {
								stores;page;constructors;components;form;${use_boundaries ? 'errors;error;' : ''}${levels.map((l) => `data_${l}`).join(';')};
								stores.page.notify();
							});
						`
						: 'afterUpdate(stores.page.notify);'
				}

				let mounted = ${isSvelte5Plus() ? '$state(false)' : 'false'};
				let navigated = ${isSvelte5Plus() ? '$state(false)' : 'false'};
				let title = ${isSvelte5Plus() ? '$state(null)' : 'null'};

				onMount(() => {
					const unsubscribe = stores.page.subscribe(() => {
						if (mounted) {
							navigated = true;
							tick().then(() => {
								title = document.title || 'untitled page';
							});
						}
					});

					mounted = true;
					return unsubscribe;
				});

				${isSvelte5Plus() ? `const Pyramid_${max_depth}=$derived(constructors[${max_depth}])` : ''}
			</script>

			${pyramid}

			{#if mounted}
				<div id="svelte-announcer" aria-live="assertive" aria-atomic="true" style="position: absolute; left: 0; top: 0; clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap; width: 1px; height: 1px">
					{#if navigated}
						{title}
					{/if}
				</div>
			{/if}
		`
	);

	if (isSvelte5Plus()) {
		write_if_changed(
			`${output}/root.js`,
			dedent`
			import { asClassComponent } from 'svelte/legacy';
			import Root from './root.svelte';
			export default asClassComponent(Root);
		`
		);
	}
}
