<script lang="ts">
	import { lib, t, auth } from "./app.svelte.js";
	import { api, asArray } from "./client.svelte.js";
	import { ui } from "./app.svelte.js";
	import TreeItem from "./TreeItem.svelte";

	let query = $state(""),
		results = $state<any>(null);

	let expanded = $state({
		artists: true,
		playlists: true,
		shared: true,
		shares: true,
	});

	let myPlaylists = $derived(
		lib.playlists.filter(
			(playlist) =>
				!playlist.owner ||
				(auth.user && playlist.owner.toLowerCase() === auth.user.toLowerCase()),
		),
	);
	let sharedPlaylists = $derived(
		lib.playlists.filter(
			(playlist) =>
				playlist.owner &&
				auth.user &&
				playlist.owner.toLowerCase() !== auth.user.toLowerCase(),
		),
	);

	$effect(() => {
		if (!query.trim()) {
			results = null;
			return;
		}
		const timer = setTimeout(async () => {
			results = (await api.search(query)) || {};
		}, 300);
		return () => clearTimeout(timer);
	});

	function onKey(event: KeyboardEvent) {
		if ((event.target as HTMLElement).tagName === "INPUT") return;
		const elements = Array.from(
			document.querySelectorAll("#library [data-id]"),
		) as HTMLElement[];
		const focusedIndex = elements.findIndex((element) =>
			element.classList.contains("focused"),
		);
		const { key, code, altKey: isAltPressed, shiftKey: isShiftPressed } = event;

		if (key === "Escape")
			return (event.preventDefault(), (lib.focusedId = null));

		const offsets: Record<string, number> = {
			ArrowDown: 1,
			ArrowUp: -1,
			PageDown: 10,
			PageUp: -10,
			Home: -Infinity,
			End: Infinity,
		};

		if (key in offsets) {
			event.preventDefault();
			const targetIndex = Math.max(
				0,
				Math.min(elements.length - 1, focusedIndex + offsets[key]),
			);
			const next = elements[targetIndex];
			if (next) {
				lib.focusedId = next.dataset.id || null;
				next.scrollIntoView({ block: "nearest" });
			}
		} else if (code === "Enter") {
			event.preventDefault();
			elements[focusedIndex]?.click();
		} else if (code === "KeyA") {
			event.preventDefault();
			elements[focusedIndex]?.dispatchEvent(
				new CustomEvent("actionadd", { detail: isAltPressed }),
			);
		} else if (
			key === "ContextMenu" ||
			key === "`" ||
			(key === "F10" && isShiftPressed)
		) {
			event.preventDefault();
			const element = elements[focusedIndex] as HTMLElement;
			if (element) {
				const bounds = element.getBoundingClientRect();
				element.dispatchEvent(
					new MouseEvent("contextmenu", {
						bubbles: true,
						cancelable: true,
						clientX: bounds.left + 50,
						clientY: bounds.bottom,
					}),
				);
			}
		}
	}
</script>

{#snippet section(
	label: string,
	id: string,
	expanded = true,
	toggle?: () => void,
	children?: any,
)}
	<li class="section">
		<button
			tabindex="-1"
			{id}
			class="label"
			class:focused={lib.focusedId === id}
			data-id={id}
			onclick={(e) => {
				e.stopPropagation();
				toggle?.();
			}}
		>
			{label}
		</button>
		{#if expanded}
			{@render children?.()}
		{/if}
	</li>
{/snippet}

<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
<nav
	id="library"
	onfocusin={() => (ui.activeContext = "library")}
	onkeydown={onKey}
	onclick={(e) => {
		if (e.target === e.currentTarget) lib.focusedId = null;
	}}
	tabindex="0"
>
	<input
		id="search"
		type="search"
		bind:value={query}
		placeholder={t("search")}
		onfocusin={(e) => e.stopPropagation()}
		onclick={(e) => e.stopPropagation()}
	/>
	<ul class="tree">
		{#if query && results}
			{#each ["artist", "album", "song"] as type}
				{@const items = asArray(results[type])}
				{#if items.length}
					{@render section(
						t((type + "s") as any),
						`search-${type}`,
						true,
						undefined,
						searchItems,
					)}
					{#snippet searchItems()}
						<ul>
							{#each items as item, itemIndex (item.id + itemIndex)}
								<TreeItem
									id={item.id}
									uid={`search-${type}-${item.id}-${itemIndex}`}
									label={item.name || item.title}
									fetcher={type === "artist"
										? () => api.artist(item.id)
										: type === "album"
											? () => api.album(item.id)
											: undefined}
									childFactory={type === "artist"
										? (a: any) => () => api.album(a.id)
										: undefined}
									isTrack={type === "song"}
									track={type === "song" ? item : null}
									type={type as any}
									{item}
								/>
							{/each}
						</ul>
					{/snippet}
				{/if}
			{/each}
		{:else}
			{@render section(
				t("artists"),
				"lib-artists",
				expanded.artists,
				() => (expanded.artists = !expanded.artists),
				artists,
			)}
			{#snippet artists()}
				<ul>
					{#each lib.artists as item, itemIndex (item.id)}
						<TreeItem
							id={item.id}
							uid={`artist-${item.id}-${itemIndex}`}
							label={item.name}
							fetcher={() => api.artist(item.id)}
							childFactory={(a: any) => () => api.album(a.id)}
							type="artist"
							{item}
						/>
					{/each}
				</ul>
			{/snippet}

			{@render section(
				t("playlists"),
				"lib-playlists",
				expanded.playlists,
				() => (expanded.playlists = !expanded.playlists),
				playlists,
			)}
			{#snippet playlists()}
				<ul>
					{#each myPlaylists as item, itemIndex (item.id)}
						<TreeItem
							id={item.id}
							uid={`playlist-${item.id}-${itemIndex}`}
							label={item.name}
							fetcher={() => api.playlist(item.id)}
							type="playlist"
							isReadonly={item.readonly === true}
							owner={item.owner}
							isPublic={item.public}
							{item}
						/>
					{/each}
				</ul>
			{/snippet}

			{#if sharedPlaylists.length}
				{@render section(
					t("shared_playlists"),
					"lib-shared-playlists",
					expanded.shared,
					() => (expanded.shared = !expanded.shared),
					shared,
				)}
				{#snippet shared()}
					<ul>
						{#each sharedPlaylists as item, itemIndex (item.id)}
							<TreeItem
								id={item.id}
								uid={`shared-playlist-${item.id}-${itemIndex}`}
								label={item.name}
								fetcher={() => api.playlist(item.id)}
								type="playlist"
								isReadonly={item.readonly === true}
								owner={item.owner}
								isPublic={item.public}
								{item}
							/>
						{/each}
					</ul>
				{/snippet}
			{/if}

			{#if lib.isSharingSupported && lib.shares.length}
				{@render section(
					t("shares"),
					"lib-shares",
					expanded.shares,
					() => (expanded.shares = !expanded.shares),
					shares,
				)}
				{#snippet shares()}
					<ul>
						{#each lib.shares as item, itemIndex (item.id)}
							<TreeItem
								id={item.id}
								uid={`share-${item.id}-${itemIndex}`}
								label={item.description || item.url}
								type="share"
								{item}
							/>
						{/each}
					</ul>
				{/snippet}
			{/if}
		{/if}
	</ul>
</nav>

<style>
	#library {
		flex: 1;
		overflow-y: auto;
		padding-block: 0.5rem;
		padding-inline: 1rem;
	}
	#search {
		inline-size: 100%;
	}
	ul {
		padding: 0;
		margin: 0;
		list-style: none;
	}
	.label {
		display: block;
		inline-size: 100%;
		text-align: start;
		padding: 0;
		padding-block-start: 0.5rem;
		padding-block-end: 0.25rem;
		background: none;
		border: none;
		border-block-end: 1px solid var(--border-text);
		font-weight: bold;
	}
	.label.focused {
		background: Highlight;
		color: HighlightText;
	}
	:global(#library:not(:focus-within)) .label.focused {
		background: GrayText;
		color: HighlightText;
	}
	button {
		color: inherit;
	}
	:global(body.dynamic) #search {
		background-color: var(--bg-secondary);
		color: var(--text);
		border: 1px solid var(--border);
		border-radius: 4px;
	}
	:global(body.dynamic) #search::placeholder {
		color: var(--text);
		opacity: 0.5;
	}
</style>
