<script lang="ts">
	import { mediaListCollection, Type, type Media } from '$lib/AniList/media';
	import type { UserIdentity, AniListAuthorisation } from '$lib/AniList/identity';
	import { onDestroy, onMount } from 'svelte';
	import { chapterCount } from '$lib/Media/Manga/chapters';
	import { pruneAllManga } from '$lib/Media/Manga/cache';
	import manga from '$stores/manga';
	import { database } from '$lib/Database/chapters';
	import settings from '$stores/settings';
	import lastPruneTimes from '$stores/lastPruneTimes';
	import ListTitle from '../ListTitle.svelte';
	import Error from '$lib/Error/RateLimited.svelte';
	import CleanMangaList from './CleanMangaList.svelte';
	import authorisedJson from '$lib/authorised.json';
	import { incrementMediaProgress } from '$lib/Media/Anime/cache';
	import { getNotificationsContext } from 'svelte-notifications';
	import { options } from '$lib/Notification/options';

	export let user: AniListAuthorisation;
	export let identity: UserIdentity;
	export let displayUnresolved: boolean;
	export let due: boolean;

	const { addNotification } = getNotificationsContext();
	const authorised = authorisedJson.includes(identity.id);
	let mangaLists: Promise<Media[]>;
	let startTime: number;
	let endTime: number;
	let lastUpdatedMedia = -1;
	let previousMangaList: Media[];
	let pendingUpdate: number | null = null;
	let progress = 0;
	let rateLimited = false;
	let forceFlag = false;

	const keyCacher = setInterval(() => {
		startTime = performance.now();
		endTime = -1;
		mangaLists = mediaListCollection(user, identity, Type.Manga, $manga, $lastPruneTimes.manga, {
			addNotification
		});
	}, $settings.cacheMinutes * 1000 * 60);

	onMount(async () => {
		startTime = performance.now();
		mangaLists = mediaListCollection(user, identity, Type.Manga, $manga, $lastPruneTimes.manga, {
			addNotification
		});
	});

	onDestroy(() => clearInterval(keyCacher));

	const cleanMedia = async (manga: Media[], displayUnresolved: boolean, force: boolean) => {
		progress = 0;

		if (manga === undefined) return [];

		if (!authorised && (await database.chapters.toArray()).length <= 0 && !force) return [];

		if (authorised) {
			let refreshing = false;

			if ($lastPruneTimes.chapters === 1) {
				refreshing = true;

				lastPruneTimes.setKey('chapters', new Date().getTime());
			} else {
				const currentDate = new Date();

				if (
					(currentDate.getTime() - $lastPruneTimes.chapters) / 1000 / 60 >
					Math.max($settings.cacheMangaMinutes, 5)
				) {
					refreshing = true;

					lastPruneTimes.setKey('chapters', currentDate.getTime());
					(async () => {
						await database.chapters.bulkDelete(
							(await database.chapters.toArray()).map((m) => m.id)
						);
					})();
				}
			}

			if (refreshing) {
				addNotification(
					options({
						heading: 'Manga',
						description: 'Re-freshing manga data ...'
					})
				);
			}
		}

		const releasingMedia = manga.filter(
			(media: Media) =>
				(due ? media.status === 'RELEASING' : media.status === 'FINISHED') &&
				(media.mediaListEntry || { status: 'DROPPED' }).status !==
					($settings.displayPausedMedia ? '' : 'PAUSED') &&
				(media.mediaListEntry || { status: 'DROPPED' }).status !== 'DROPPED' &&
				(media.mediaListEntry || { progress: 0 }).progress >=
					($settings.displayNotStarted === true ? 0 : 1)
		);
		let finalMedia = releasingMedia;
		const progressStep = 100 / finalMedia.length / 2;
		const chapterPromises = finalMedia.map((m: Media) =>
			database.chapters.get(m.id).then((c) => {
				if (progress < 100) progress += progressStep;

				if (c !== undefined) return chapterCount(identity, m, $settings.calculateGuessingDisabled);
				else {
					// A = On 1 second interval,
					// B = a maximum of 5 requests per second are allowed.
					// C = chapterCount makes 3 requests per call.
					// F = A / (B / C) = 0.6 seconds
					return new Promise((resolve) => setTimeout(resolve, 600)).then(() =>
						chapterCount(identity, m, $settings.calculateGuessingDisabled)
					);
				}
			})
		);
		const chapterCounts: (number | null)[] = [];

		for (let i = 0; i < chapterPromises.length; i++) {
			const count = await chapterPromises[i];

			if (count === -22) {
				rateLimited = true;

				break;
			}

			chapterCounts.push(count);

			if (progress < 100) progress += progressStep;
		}

		finalMedia.forEach((m: Media, i) => (m.episodes = chapterCounts[i] || -1337));

		if (!displayUnresolved) finalMedia = finalMedia.filter((m: Media) => m.episodes !== -1337);

		finalMedia.sort(
			(a: Media, b: Media) =>
				(a.episodes || 9999) -
				(a.mediaListEntry || { progress: 0 }).progress -
				((b.episodes || 9999) - (b.mediaListEntry || { progress: 0 }).progress)
		);

		finalMedia = finalMedia.filter(
			(item, index, array) =>
				array.findIndex((i) => i.id === item.id) === index &&
				(item.episodes === -1337 && displayUnresolved
					? true
					: (item.mediaListEntry?.progress || 0) <
					  ($settings.calculateChaptersRoundedDown === true
							? Math.floor(item.episodes)
							: item.episodes))
		);

		if (!endTime || endTime === -1) endTime = performance.now() - startTime;

		return finalMedia;
	};

	const updateMedia = async (id: number, progress: number | undefined, media: Media[]) => {
		pendingUpdate = id;
		lastUpdatedMedia = id;

		await database.chapters.delete(id);

		incrementMediaProgress(id, progress, user, () => {
			previousMangaList = media;

			const foundEntry = media.find((m) => m.id === id);

			if (foundEntry && foundEntry.mediaListEntry)
				foundEntry.mediaListEntry.progress = (progress || 0) + 1;

			mangaLists = mediaListCollection(user, identity, Type.Manga, $manga, $lastPruneTimes.manga, {
				forcePrune: true
			});
			pendingUpdate = null;
		});
	};

	const cleanCache = () => {
		startTime = performance.now();
		endTime = -1;

		pruneAllManga().then(() => {
			mangaLists = mediaListCollection(user, identity, Type.Manga, $manga, $lastPruneTimes.manga, {
				forcePrune: true
			});
		});
	};
</script>

{#await mangaLists}
	{#if !authorised}
		<ListTitle count={0} time={endTime / 1000}>
			<button
				data-umami-event="Force Refresh Manga"
				title="Force a full refresh"
				on:click={() => {
					cleanCache();

					forceFlag = true;
				}}>Refresh</button
			>
		</ListTitle>
	{/if}

	{#if previousMangaList}
		<CleanMangaList
			media={previousMangaList}
			{cleanCache}
			{lastUpdatedMedia}
			{updateMedia}
			{endTime}
			{pendingUpdate}
			{due}
			{rateLimited}
			{authorised}
		/>
	{:else}
		{#if authorised}
			<ListTitle />
		{/if}

		<ul><li>Loading {progress.toFixed(0)}% ...</li></ul>
	{/if}
{:then media}
	{#await cleanMedia(media, displayUnresolved, forceFlag)}
		{#if !authorised}
			<ListTitle count={0} time={endTime / 1000}>
				<button
					data-umami-event="Force Refresh Manga"
					title="Force a full refresh"
					on:click={() => {
						cleanCache();

						forceFlag = true;
					}}>Refresh</button
				>
			</ListTitle>
		{/if}

		{#if previousMangaList}
			<CleanMangaList
				media={previousMangaList}
				{cleanCache}
				{lastUpdatedMedia}
				{updateMedia}
				{endTime}
				{pendingUpdate}
				{due}
				{rateLimited}
				{authorised}
			/>
		{:else}
			{#if authorised}
				<ListTitle />
			{/if}

			<ul><li>Loading {progress.toFixed(0)}% ...</li></ul>
		{/if}
	{:then cleanedMedia}
		{#if !authorised}
			<ListTitle count={cleanedMedia.length} time={endTime / 1000}>
				<button
					data-umami-event="Force Refresh Manga"
					title="Force a full refresh"
					on:click={() => {
						cleanCache();

						forceFlag = true;
					}}>Refresh</button
				>
			</ListTitle>
		{/if}

		<CleanMangaList
			media={cleanedMedia}
			{cleanCache}
			{lastUpdatedMedia}
			{updateMedia}
			{endTime}
			{pendingUpdate}
			{due}
			{rateLimited}
			{authorised}
		/>
	{:catch}
		{#if authorised}
			<ListTitle count={-1337} time={0} />
		{/if}

		<Error list={false} />
	{/await}
{/await}
