<script lang="ts">
import localforage from "localforage";
import { onDestroy, onMount } from "svelte";
import { browser } from "$app/environment";
import { env } from "$env/dynamic/public";
import { hydrateMediaListCache } from "$lib/Data/AniList/cacheHydration";
import type { AniListAuthorisation } from "$lib/Data/AniList/identity";
import { type Media, mediaListCollection, Type } from "$lib/Data/AniList/media";
import { database } from "$lib/Database/IDB/chapters";
import RateLimitedError from "$lib/Error/RateLimited.svelte";
import Skeleton from "$lib/Loading/Skeleton.svelte";
import { incrementMediaProgress } from "$lib/Media/Anime/cache";
import { pruneAllManga } from "$lib/Media/Manga/cache";
import { chapterCount, hydrateChapterCounts } from "$lib/Media/Manga/chapters";
import { options } from "$lib/Notification/options";
import { addNotification } from "$lib/Notification/store";
import privilegedUser from "$lib/Utility/privilegedUser";
import identity from "$stores/identity";
import lastPruneTimes from "$stores/lastPruneTimes";
import locale from "$stores/locale";
import { get } from "svelte/store";
import manga from "$stores/manga";
import revalidateManga from "$stores/revalidateManga";
import settings from "$stores/settings";
import ListTitle from "../ListTitle.svelte";
import CleanMangaList from "./CleanMangaList.svelte";

export let user: AniListAuthorisation = {
	accessToken: "",
	refreshToken: "",
	expiresIn: 0,
	tokenType: "",
};
export let displayUnresolved: boolean;
export let due: boolean;
export let dummy = $settings.debugDummyLists || false;
export let dummyCount = 7;
export let disableFilter = false;
export let limit: number | undefined = undefined;
let authorised = false;
let mangaLists: Promise<Media[]> = new Promise(() => {});
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;
let lastListSize = 5;
let keyCacher: ReturnType<typeof setInterval> | undefined;
let keyCacheMinutes = -1;
let nativeEnhancementVersion = 0;
let nativeEnhancementKey = "";

$: authorised =
	env.PUBLIC_ALWAYS_REFRESH_MANGA === "true" || privilegedUser($identity.id);

const restartKeyCacher = (cacheMinutes: number) => {
	if (keyCacher) clearInterval(keyCacher);

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

onMount(async () => {
	await hydrateMediaListCache("manga");
	restartKeyCacher(Math.max($settings.cacheMangaMinutes, 5));

	if (browser) {
		const lastStoredList = (await localforage.getItem(
			`last${due ? "" : "Completed"}MangaListLength`,
		)) as number | null;

		if (lastStoredList) lastListSize = parseInt(String(lastStoredList), 10);
	}

	startTime = performance.now();

	if (dummy) {
		const sampleManga = (await (
			await fetch("/sample-media/manga.json")
		).json()) as Media[];

		// Use deterministic selection for consistent display
		const filtered = sampleManga.filter(
			(manga) =>
				manga.chapters &&
				!manga.tags.some((tag) => tag.name === "Nudity") &&
				!manga.tags.some((tag) => tag.name === "Rape") &&
				!manga.tags.some((tag) => tag.name === "Tragedy") &&
				!manga.tags.some((tag) => tag.name === "Bondage") &&
				!manga.genres.some((genre) => genre === "Hentai") &&
				manga.genres.some((genre) => genre === "Comedy") &&
				manga.status !== "NOT_YET_RELEASED",
		);
		mangaLists = Promise.resolve(
			filtered.slice(0, dummyCount).map((manga) => {
				const mediaListEntry =
					manga.mediaListEntry ||
					({ progress: 0 } as NonNullable<Media["mediaListEntry"]>);

				manga.status = "FINISHED";
				manga.episodes = Math.floor((manga.chapters || 10) * 0.7);
				mediaListEntry.progress = Math.floor((manga.episodes || 5) * 0.5) + 1;
				manga.mediaListEntry = mediaListEntry;
				return manga;
			}) as unknown as Media[],
		);
	} else {
		mangaLists = mediaListCollection(
			user,
			$identity,
			Type.Manga,
			$manga,
			$lastPruneTimes.manga,
			{
				addNotification,
			},
		);
	}
});

$: if (
	keyCacher &&
	keyCacheMinutes !== Math.max($settings.cacheMangaMinutes, 5)
)
	restartKeyCacher(Math.max($settings.cacheMangaMinutes, 5));

let lastMangaRevalidation = 0;

$: if (!dummy && $revalidateManga > lastMangaRevalidation) {
	lastMangaRevalidation = $revalidateManga;

	startTime = performance.now();
	endTime = -1;

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

onDestroy(() => {
	if (keyCacher) clearInterval(keyCacher);
});

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

	if (manga && dummy) return manga;

	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", Date.now());
		} 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: get(locale)().notifications?.mangaHeading ?? "Manga",
					description:
						get(locale)().notifications?.mangaRefreshing ??
						"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 chapterCounts: (number | null)[] = [];
	void nativeVersion;

	if (due && finalMedia.length > 0) {
		const progressiveNative =
			$settings.calculatePreferNativeChapterCount === true;
		let startDeferredNative = false;

		if (progressiveNative) {
			const enhancementKey = finalMedia
				.map((media) => `${media.id}:${media.mediaListEntry?.progress || 0}`)
				.join("|");

			if (nativeEnhancementKey !== enhancementKey) {
				nativeEnhancementKey = enhancementKey;
				startDeferredNative = true;
			}
		}

		const hydration = await hydrateChapterCounts(
			$identity,
			finalMedia,
			$settings.calculateGuessingDisabled,
			{
				deferNative: progressiveNative,
				startDeferredNative,
				onNativeChunkResolved: () => {
					nativeEnhancementVersion += 1;
				},
			},
		);

		rateLimited = hydration.rateLimited;
		progress = 50;
	}

	for (let i = 0; i < finalMedia.length; i++) {
		const media = finalMedia[i];
		const progressStep = finalMedia.length > 0 ? 50 / finalMedia.length : 0;
		const count = due
			? await chapterCount(
					$identity,
					media,
					$settings.calculateGuessingDisabled,
				)
			: media.chapters;

		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 previousMangaList}
    <CleanMangaList
      media={previousMangaList}
      {cleanCache}
      {lastUpdatedMedia}
      {updateMedia}
      {endTime}
      {pendingUpdate}
      {due}
      {rateLimited}
      {authorised}
      {dummy}
      {disableFilter}
      {limit}
    />
  {:else}
    {#if !authorised}
      <ListTitle
        count={0}
        time={endTime / 1000}
        title={$locale().lists.due.mangaAndLightNovels}
        hideTime={dummy}
        hideCount={dummy}
      >
        {#if !dummy}
          <button
            data-umami-event="Force Refresh Manga"
            title="Force a full refresh"
            onclick={() => {
              cleanCache();

              forceFlag = true;
            }}>Refresh</button
          >
        {/if}
      </ListTitle>
    {:else}
      <ListTitle
        {progress}
        title={$locale().lists.due.mangaAndLightNovels}
        hideTime={dummy}
        hideCount={dummy}
      />
    {/if}

    <Skeleton card={false} count={lastListSize} height="0.9rem" list />
  {/if}
{:then media}
  {#await cleanMedia(media, displayUnresolved, forceFlag, nativeEnhancementVersion)}
    {#if previousMangaList}
      <CleanMangaList
        media={previousMangaList}
        {cleanCache}
        {lastUpdatedMedia}
        {updateMedia}
        {endTime}
        {pendingUpdate}
        {due}
        {rateLimited}
        {authorised}
        {dummy}
        {disableFilter}
        {limit}
      />
    {:else}
      {#if !authorised}
        <ListTitle
          count={0}
          time={endTime / 1000}
          title={$locale().lists.due.mangaAndLightNovels}
          hideTime={dummy}
          hideCount={dummy}
        >
          {#if !dummy}
            <button
              data-umami-event="Force Refresh Manga"
              title="Force a full refresh"
              onclick={() => {
                cleanCache();

                forceFlag = true;
              }}>Refresh</button
            >
          {/if}
        </ListTitle>
      {:else}
        <ListTitle
          {progress}
          title={$locale().lists.due.mangaAndLightNovels}
          hideTime={dummy}
          hideCount={dummy}
        />
      {/if}

      <Skeleton card={false} count={lastListSize} height="0.9rem" list />
    {/if}
  {:then cleanedMedia}
    {#if !authorised}
      <ListTitle
        count={cleanedMedia.length}
        time={endTime / 1000}
        title={$locale().lists.due.mangaAndLightNovels}
        hideTime={dummy}
        hideCount={dummy}
      >
        {#if !dummy}
          <button
            data-umami-event="Force Refresh Manga"
            title="Force a full refresh"
            onclick={() => {
              cleanCache();

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

    <CleanMangaList
      media={cleanedMedia}
      {cleanCache}
      {lastUpdatedMedia}
      {updateMedia}
      {endTime}
      {pendingUpdate}
      {due}
      {rateLimited}
      {authorised}
      {dummy}
      {disableFilter}
      {limit}
    />
  {:catch}
    {#if authorised}
      <ListTitle
        count={-1337}
        time={0}
        title={$locale().lists.due.mangaAndLightNovels}
        hideTime={dummy}
        hideCount={dummy}
      />
    {/if}

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