<script lang="ts">
	import { browser } from "$app/environment";

	type PortalProps = {
		/**
		 * Content to render into the portal target.
		 */
		children: import("svelte").Snippet;

		/**
		 * Optional CSS selector for the portal target element.
		 * Defaults to `document.body`.
		 */
		targetSelector?: string;

		/**
		 * Optional tag name used for the created mount node.
		 * Defaults to `div`.
		 */
		tagName?: keyof HTMLElementTagNameMap;

		/**
		 * Optional class applied to the created mount node.
		 */
		class?: string;

		/**
		 * Optional id applied to the created mount node.
		 */
		id?: string;
	};

	let {
		children,
		targetSelector,
		tagName = "div",
		class: className,
		id,
	}: PortalProps = $props();

	function createMountNode() {
		const el = document.createElement(tagName);
		if (className) el.className = className;
		if (id) el.id = id;
		return el;
	}

	function resolveTarget(): Element | null {
		if (!browser) return null;
		if (targetSelector) return document.querySelector(targetSelector);
		return document.body;
	}

	/**
	 * Attachment factory: creates a mount node and appends it to the target.
	 * MUST return either:
	 * - void, or
	 * - a cleanup function, or
	 * - an object with a destroy() method
	 *
	 * The TS types in this project expect the cleanup-function form.
	 */
	function portal(node: HTMLElement) {
		const target = resolveTarget();
		if (!target) return;

		const mount = createMountNode();
		target.appendChild(mount);

		// Move the node into the mount (portaling)
		mount.appendChild(node);

		return () => {
			// Remove mount (also removes `node` from DOM)
			mount.remove();
		};
	}
</script>

{#if browser}
	<div {@attach portal}>
		{@render children()}
	</div>
{/if}
