<script lang="ts">
	import { parseYaml } from "obsidian";
	import { ReadingCalendarSettingsSchema } from "./ReadingCalendarCodeBlock";
	import type { SvelteCodeBlockProps } from "./SvelteCodeBlockRenderer";
	import {
		createSettings,
		setSettingsContext,
	} from "@ui/stores/settings.svelte";
	import {
		createMetadata,
		setMetadataContext,
	} from "@ui/stores/metadata.svelte";
	import {
		createReadingLog,
		setReadingLogContext,
	} from "@ui/stores/reading-log.svelte";
	import { ArrowLeft, ArrowRight } from "lucide-svelte";
	import { onMount } from "svelte";

	const { plugin, source }: SvelteCodeBlockProps = $props();

	const settings = ReadingCalendarSettingsSchema.parse(parseYaml(source));

	const settingsStore = createSettings(plugin);
	setSettingsContext(settingsStore);

	const metadataStore = createMetadata(plugin, {
		statusFilter: null,
	});
	setMetadataContext(metadataStore);

	const readingLog = createReadingLog(plugin.readingLog);
	setReadingLogContext(readingLog);

	let year = $state(new Date().getFullYear());
	let month = $state(new Date().getMonth());

	$effect(() => {
		readingLog.filterYear = year;
		readingLog.filterMonth = month + 1;
	});

	const monthNames = [
		"January",
		"February",
		"March",
		"April",
		"May",
		"June",
		"July",
		"August",
		"September",
		"October",
		"November",
		"December",
	];

	const daysOfWeek = [
		"Sunday",
		"Monday",
		"Tuesday",
		"Wednesday",
		"Thursday",
		"Friday",
		"Saturday",
	];

	// @ts-expect-error Moment is provided by Obsidian
	let today = $state(moment());

	function msUntilMidnight() {
		// @ts-expect-error Moment is provided by Obsidian
		return moment().endOf("day").diff(today, "milliseconds");
	}

	function updateToday() {
		setTimeout(() => {
			// @ts-expect-error Moment is provided by Obsidian
			today = moment();
			updateToday();
		}, msUntilMidnight() + 1000);
	}

	onMount(() => {
		updateToday();
	});

	const weeks = $derived.by(() => {
		// @ts-expect-error Moment is provided by Obsidian
		const firstDay = moment()
			.year(year)
			.month(month)
			.startOf("month")
			.startOf("week");

		// @ts-expect-error Moment is provided by Obsidian
		const lastDay = moment()
			.year(year)
			.month(month)
			.endOf("month")
			.endOf("week");

		const weeks = [];
		let currentDay = firstDay.clone();
		while (currentDay.isBefore(lastDay)) {
			const week = [];
			for (let i = 0; i < 7; i++) {
				week.push(currentDay.clone());
				currentDay.add(1, "day");
			}
			weeks.push(week);
		}

		return weeks;
	});

	interface CoverData {
		src: string;
		alt: string;
	}

	interface BookMapItem {
		totalPagesRead: number;
		covers: CoverData[];
	}

	const bookMap = $derived.by(() => {
		const bookMap = new Map<number, BookMapItem>();
		for (const item of readingLog.entries) {
			const key = item.createdAt.date();

			let coverPath = metadataStore.metadata.find(
				(entry) => entry.file.basename === item.book,
			)?.frontmatter?.[settings.coverProperty];

			coverPath = plugin.app.vault.getFileByPath(coverPath);

			if (!coverPath) {
				continue;
			}

			coverPath = plugin.app.vault.getResourcePath(coverPath);

			const value = bookMap.get(key) ?? { totalPagesRead: 0, covers: [] };
			value.totalPagesRead += item.pagesRead;
			value.covers.push({ src: coverPath, alt: item.book });
			bookMap.set(key, value);
		}
		return bookMap;
	});
</script>

<div class="reading-calendar">
	<div class="controls">
		<div class="left">
			<button
				class="prev-month"
				aria-label="Go to previous month"
				onclick={() => {
					if (month === 0) {
						month = 11;
						year--;
					} else {
						month--;
					}
				}}
			>
				<ArrowLeft />
			</button>
			<button
				class="today"
				aria-label="Go to the current month"
				onclick={() => {
					year = today.year();
					month = today.month();
				}}
			>
				Today
			</button>
		</div>
		<h2>{monthNames[month]} {year}</h2>
		<button
			class="next-month"
			aria-label="Go to next month"
			onclick={() => {
				if (month === 11) {
					month = 0;
					year++;
				} else {
					month++;
				}
			}}
		>
			<ArrowRight />
		</button>
	</div>
	<table>
		<thead>
			<tr>
				{#each daysOfWeek as day}
					<th>{day}</th>
				{/each}
			</tr>
		</thead>
		<tbody>
			{#each weeks as week}
				<tr>
					{#each week as day}
						{@const isThisMonth = day.month() === month}
						<td
							class:is-today={day.isSame(today, "day")}
							class:is-weekend={day.day() === 0 ||
								day.day() === 6}
							class:is-prev-month={day.month() ===
								(month === 0 ? 11 : month - 1)}
							class:is-next-month={day.month() ===
								(month + 1) % 12}
						>
							<div class="header">
								<span>{day.date()}</span>
								{#if isThisMonth && bookMap.has(day.date())}
									{@const data = bookMap.get(day.date())!}
									<span class="total-pages-read">
										Pages: {data.totalPagesRead}
									</span>
								{/if}
							</div>
							<div class="covers">
								{#if isThisMonth && bookMap.has(day.date())}
									{@const data = bookMap.get(day.date())!}
									{#each data.covers as cover}
										{#if cover}
											<img
												src={cover.src}
												alt={cover.alt}
											/>
										{/if}
									{/each}
								{/if}
							</div>
						</td>
					{/each}
				</tr>
			{/each}
		</tbody>
	</table>
</div>

<style lang="scss">
	$cell-padding: var(--size-4-2);

	.reading-calendar {
		display: flex;
		flex-direction: column;
		overflow: auto;

		.controls {
			min-width: 800px;
			display: grid;
			grid-template-columns: max-content 1fr max-content;
			gap: var(--size-4-4);
			align-items: center;

			h2 {
				text-align: center;
				margin: 0;
			}

			.left {
				display: flex;
				gap: var(--size-2-2);
				align-items: center;
			}
		}
	}

	table {
		table-layout: fixed;
		border-collapse: collapse;
		min-width: 800px;

		th {
			padding: $cell-padding;
			width: calc(100% / 7);
		}

		td {
			padding: $cell-padding;

			&.is-prev-month,
			&.is-next-month {
				color: var(--text-faint);
			}

			&.is-today {
				color: var(--text-accent);
			}

			.header {
				display: flex;
				justify-content: space-between;
				align-items: center;

				.total-pages-read {
					font-size: var(--font-smallest);
					color: var(--text-muted);
				}
			}

			.covers {
				position: relative;
				width: 100%;
				aspect-ratio: 2 / 3;

				img {
					border-radius: var(--radius-l);
				}

				&:has(img:first-child:nth-last-child(1)) {
					img {
						position: absolute;
						height: 100%;
						width: 100%;
					}
				}

				&:has(img:first-child:nth-last-child(2)) {
					img:first-child {
						position: absolute;
						height: 100%;
						width: 100%;
						clip-path: polygon(0 0, 100% 0, 0 100%);
					}

					img:last-child {
						position: absolute;
						height: 100%;
						width: 100%;
						clip-path: polygon(100% 100%, 100% 0, 0 100%);
					}
				}

				&:has(img:first-child:nth-last-child(3)) {
					img:first-child {
						position: absolute;
						height: 100%;
						width: 100%;
						clip-path: polygon(0 0, 100% 0, 100% 20%, 0 50%);
					}

					img:nth-child(2) {
						position: absolute;
						height: 100%;
						width: 100%;
						clip-path: polygon(100% 80%, 100% 20%, 0 50%);
					}

					img:last-child {
						position: absolute;
						height: 100%;
						width: 100%;
						clip-path: polygon(0 50%, 100% 80%, 100% 100%, 0% 100%);
					}
				}

				&:has(img:first-child:nth-last-child(4)) {
					img:first-child {
						position: absolute;
						height: 50%;
						width: 50%;
						top: 0;
						left: 0;
					}

					img:nth-child(2) {
						position: absolute;
						height: 50%;
						width: 50%;
						top: 0;
						right: 0;
					}

					img:nth-child(3) {
						position: absolute;
						height: 50%;
						width: 50%;
						bottom: 0;
						left: 0;
					}

					img:last-child {
						position: absolute;
						height: 50%;
						width: 50%;
						bottom: 0;
						right: 0;
					}
				}
			}
		}
	}
</style>
