<script lang="ts">
	import { untrack } from "svelte";
	import { api } from "./client.svelte.js";
	import {
		insertAt,
		queue,
		auth,
		loadLib,
		player,
		recursiveSongs,
		settings,
		lib,
		t,
	} from "./app.svelte.js";
	import { ui } from "./app.svelte.js";
	import ContextMenu from "./ContextMenu.svelte";
	import TreeItem from "./TreeItem.svelte";
	import icAdd from "../assets/famfamfam-silk-svg/icons/Add.svg";
	import icDisk from "../assets/famfamfam-silk-svg/icons/Disk.svg";
	import icBinEmpty from "../assets/famfamfam-silk-svg/icons/Bin empty.svg";
	import icWorld from "../assets/famfamfam-silk-svg/icons/World.svg";
	import icHeart from "../assets/famfamfam-silk-svg/icons/Heart.svg";
	import icLink from "../assets/famfamfam-silk-svg/icons/Link.svg";

	let {
		id,
		uid = id,
		label,
		fetcher,
		childFactory,
		isTrack = false,
		track = null,
		type = "artist",
		isReadonly = false,
		owner = null,
		isPublic = false,
		item = null,
	} = $props<{
		id: string;
		uid?: string;
		label: string;
		fetcher?: () => Promise<any[]>;
		childFactory?: (item: any) => (() => Promise<any[]>) | undefined;
		isTrack?: boolean;
		track?: any;
		type?: "artist" | "album" | "playlist" | "song" | "share";
		isReadonly?: boolean;
		owner?: string | null;
		isPublic?: boolean;
		item?: any;
	}>();

	let open = $state(false),
		items = $state<any[] | null>(null),
		busy = $state(false),
		menu = $state<{ x: number; y: number } | null>(null);

	let publicOverride = $state<boolean | null>(null);
	let isPublicState = $derived(publicOverride ?? isPublic);

	let starredOverride = $state<string | undefined | null>(null);
	let isStarred = $derived(starredOverride ?? item?.starred);

	async function doToggleStar() {
		if (type !== "album" && type !== "artist") return;
		const next = !isStarred;
		try {
			const params = { id };
			starredOverride = next ? new Date().toISOString() : undefined;
			next ? await api.star(params) : await api.unstar(params);
			if (item) item.starred = starredOverride;
			starredOverride = null;
		} catch (err) {
			console.error(err);
			starredOverride = null;
		}
	}

	async function doTogglePublic() {
		if (type !== "playlist" || (owner !== auth.user && !auth.admin)) return;
		try {
			const next = !isPublicState;
			await api.updatePlaylist(id, undefined, undefined, undefined, next);
			publicOverride = next;
			await loadLib();
			publicOverride = null;
		} catch (err) {
			console.error(err);
		}
	}

	$effect(() => {
		const signal = ui.lastUpdatedPlaylistId;
		if (type === "playlist" && signal?.startsWith(id + ":")) {
			if (untrack(() => open) && fetcher) {
				busy = true;
				fetcher()
					.then((res: any[]) => (items = res))
					.catch((err: any) => console.error(err))
					.finally(() => (busy = false));
			} else {
				items = null;
			}
		}
	});

	async function doAdd(next: boolean) {
		const index = next ? queue.pos + 1 : queue.tracks.length;
		if (isTrack && track) {
			insertAt(index, { ...track });
		} else if (fetcher) {
			busy = true;
			try {
				insertAt(index, await recursiveSongs(await fetcher(), childFactory));
			} catch (err) {
				console.error(err);
			} finally {
				busy = false;
			}
		}
	}

	async function toggle(e?: Event) {
		e?.stopPropagation();
		if (isTrack) return;
		open = !open;
		if (open && !items && fetcher) {
			busy = true;
			try {
				items = await fetcher();
			} finally {
				busy = false;
			}
		}
	}

	function doUpdate() {
		if (canUpdate) {
			ui.confirmUpdatePlaylistId = id;
			ui.confirmUpdatePlaylistName = label;
		}
	}

	function doRename() {
		if (canDelete) {
			ui.renamePlaylistId = id;
			ui.renamePlaylistName = label;
		}
	}

	async function doDelete() {
		if (type === "share") {
			try {
				await api.deleteShare(id);
				await loadLib();
			} catch (err) {
				console.error(err);
			}
			return;
		}
		if (canDelete) {
			ui.confirmDeletePlaylistId = id;
			ui.confirmDeletePlaylistName = label;
		}
	}

	function keyboardActions(node: HTMLElement) {
		const onAdd = (e: any) => doAdd(e.detail);
		node.addEventListener("actionadd", onAdd);
		return {
			destroy: () => node.removeEventListener("actionadd", onAdd),
		};
	}

	function onDragStart(e: DragEvent) {
		e.dataTransfer!.effectAllowed = "copy";
		e.dataTransfer!.setData("application/tinysub-library", "");
		(window as any)._tinysub_drag = async () => {
			if (isTrack && track) return { ...track };
			if (fetcher) return recursiveSongs(await fetcher(), childFactory);
			return [];
		};
	}

	function onDragEnd() {
		delete (window as any)._tinysub_drag;
	}

	const artSize = $derived(
		(
			{
				artist: settings.artArtist,
				album: settings.artAlbum,
				playlist: settings.artPlaylist,
			} as any
		)[type] ?? 0,
	);

	const isOwner = $derived(!owner || (auth.user && owner === auth.user));
	const canUpdate = $derived(
		type === "playlist" &&
			(auth.admin ? (isOwner ? !isReadonly : true) : isOwner && !isReadonly),
	);
	const canDelete = $derived(
		type === "playlist" && (auth.admin || (isOwner && !isReadonly)),
	);
</script>

<li>
	<div class="row">
		<button
			tabindex="-1"
			class:focused={lib.focusedId === uid}
			class="btn main"
			data-id={uid}
			draggable={true}
			onclick={toggle}
			ondragend={onDragEnd}
			ondragstart={onDragStart}
			oncontextmenu={(e) => {
				e.preventDefault();
				menu = { x: e.clientX, y: e.clientY };
			}}
			use:keyboardActions
		>
			{#if artSize > 0}
				{@const artId = track?.albumId || track?.coverArt || id}
				<div class="art-container">
					<img
						alt=""
						class="art"
						loading="lazy"
						src={api.art(artId, artSize)}
						srcset="{api.art(artId, artSize)} 1x, {api.art(
							artId,
							artSize * 2,
						)} 2x"
						style="inline-size: {artSize}px; block-size: {artSize}px;"
					/>
					{#if type === "playlist" && isPublicState}
						<img alt="" class="badge" src={icWorld} title={t("public")} />
					{/if}
					{#if (type === "album" || type === "artist") && isStarred}
						<img alt="" class="badge" src={icHeart} title={t("favorite")} />
					{/if}
				</div>
			{/if}
			<div class="label-stack">
				<span class="label">{label}</span>
				{#if owner && type === "playlist" && owner.toLowerCase() !== auth.user?.toLowerCase()}
					<span class="owner">owner: {owner}</span>
				{/if}
			</div>
		</button>
		{#if canUpdate}
			<button
				tabindex="-1"
				class="btn action"
				onclick={(e) => {
					e.stopPropagation();
					doUpdate();
				}}
				title={t("update_playlist")}
			>
				<img alt="" src={icDisk} />
			</button>
		{/if}
		{#if canDelete || type === "share"}
			<button
				tabindex="-1"
				class="btn action"
				onclick={(e) => {
					e.stopPropagation();
					doDelete();
				}}
				title={type === "share" ? t("delete_share") : t("delete_playlist")}
			>
				<img alt="" src={icBinEmpty} />
			</button>
		{/if}
		{#if type === "share"}
			<button
				tabindex="-1"
				class="btn action"
				onclick={(e) => {
					e.stopPropagation();
					if (item?.url) navigator.clipboard.writeText(item.url);
				}}
				title={t("copy_link")}
			>
				<img alt="" src={icLink} />
			</button>
		{/if}
		{#if type !== "share"}
			<button
				tabindex="-1"
				class="btn action"
				onclick={(e) => {
					e.stopPropagation();
					doAdd(false);
				}}
				title={t("add_to_queue")}
			>
				<img alt="" src={icAdd} />
			</button>
		{/if}
		<button
			tabindex="-1"
			class="btn touch"
			onclick={(e) => {
				e.stopPropagation();
				menu = { x: window.innerWidth, y: e.clientY };
			}}
			title={t("context_menu")}
		>
			⋮
		</button>
	</div>

	{#if open}
		<ul>
			{#if busy}
				<li>{t("busy")}</li>
			{:else if items}
				{#each items as item, i (item.id + i)}
					<TreeItem
						childFactory={undefined}
						fetcher={childFactory?.(item)}
						id={item.id}
						isTrack={type !== "artist"}
						{item}
						label={item.name || item.title}
						track={type !== "artist" ? item : null}
						type={type === "artist" ? "album" : "song"}
						uid={id + ":" + item.id + ":" + i}
					/>
				{/each}
			{/if}
		</ul>
	{/if}
</li>

{#if menu}
	<ContextMenu
		onclose={() => (menu = null)}
		x={menu.x}
		y={menu.y}
		items={[
			type !== "share" && { label: t("add"), action: () => doAdd(false) },
			type !== "share" && { label: t("add_next"), action: () => doAdd(true) },
			type !== "share" && { type: "separator" },
			lib.isSharingSupported &&
				type !== "share" && {
					label: t("share"),
					action: () => {
						ui.shareItemId = id;
						ui.shareItemLabel = label;
						ui.shareItemType = type;
					},
				},
			type === "share" && {
				label: t("copy_link"),
				action: () => {
					if (item?.url) navigator.clipboard.writeText(item.url);
				},
			},
			...(type === "album" || type === "artist"
				? [
						{
							label: isStarred ? t("unfavorite") : t("favorite"),
							action: doToggleStar,
						},
					]
				: []),
			...(type === "playlist"
				? [
						...(canUpdate ? [{ label: t("update"), action: doUpdate }] : []),
						...(canDelete
							? [
									{ label: t("rename"), action: doRename },
									...(isOwner || auth.admin
										? [
												{
													label: isPublicState
														? t("make_private")
														: t("make_public"),
													action: doTogglePublic,
												},
											]
										: []),
									{ label: t("delete"), action: doDelete },
								]
							: []),
					]
				: []),
			type === "share" && { label: t("delete"), action: doDelete },
		].filter(Boolean) as any}
	/>
{/if}

<style>
	ul {
		list-style: none;
		padding-inline-start: 1rem;
	}
	.row {
		display: flex;
		align-items: center;
		gap: 0.25rem;
		margin-block-start: 0.5rem;
	}
	.btn.main.focused {
		background: var(--bg-highlight);
		color: var(--text-highlight);
	}
	:global(#library:not(:focus-within)) .btn.main.focused {
		background: GrayText;
		color: HighlightText;
	}
	.btn {
		background: none;
		border: none;
		display: flex;
		align-items: center;
	}
	.main {
		flex: 1;
		overflow: hidden;
		gap: 0.5rem;
	}
	@media (pointer: coarse) {
		.action {
			display: none;
		}
	}
	.art {
		object-fit: cover;
	}
	.touch {
		display: none;
	}
	@media (pointer: coarse) {
		.touch {
			display: block;
			padding: 0.5rem;
			margin: -0.5rem;
		}
	}
	.label {
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
		inline-size: 100%;
		text-align: start;
	}
	.label-stack {
		display: flex;
		flex-direction: column;
		align-items: flex-start;
		overflow: hidden;
		flex: 1;
	}
	.owner {
		font-size: 0.8rem;
		opacity: 0.75;
	}
	.art-container {
		position: relative;
	}
	.badge {
		position: absolute;
		inset-block-end: 0;
		inset-inline-end: 0;
	}
	img {
		display: block;
	}
	button {
		padding: 0;
		color: inherit;
	}
	:global(body.rounded-art) .art {
		border-radius: 2px;
	}
</style>
