<script lang="ts">
import Spacer from "$lib/Layout/Spacer.svelte";
import locale from "$stores/locale";

import settings from "$stores/settings";
import type { Media } from "$lib/Data/AniList/media";
import { cleanCache, incrementMediaProgress } from "$lib/Media/Anime/cache";
import { totalEpisodes } from "$lib/Media/Anime/episodes";
import type { AniListAuthorisation } from "$lib/Data/AniList/identity";
import ListTitle from "../ListTitle.svelte";
import { onDestroy, onMount } from "svelte";
import AiringTime from "$lib/Media/Anime/Airing/AiringTime.svelte";
import { hasDueEpisodes } from "$lib/Media/Anime/Airing/classify";
import { browser } from "$app/environment";
import identity from "$stores/identity";
import "../covers.css";
import revalidateAnime from "$stores/revalidateAnime";
import CleanGrid from "$lib/List/CleanGrid.svelte";
import CleanList from "../CleanList.svelte";
import stateBin from "$stores/stateBin";
import localforage from "localforage";
import MediaRoulette from "../MediaRoulette.svelte";
import type { Title } from "../mediaTitle";
import { track } from "$lib/analytics";

export let media: Media[];
export let title: Title;
export let animeLists: Promise<Media[]>;
export let user: AniListAuthorisation;
export let endTime: number;
export let lastUpdatedMedia: number;
export let completed = false;
export let previousAnimeList: Media[];
export let pendingUpdate: number | null;
export let upcoming = false;
export let notYetReleased = false;
export let dummy = false;
export let disableFilter = false;
export let limit: number | undefined = undefined;

let showRoulette = false;
let airingRefreshTimeout: ReturnType<typeof setTimeout> | undefined;
let scheduledAiringAt: number | null = null;
let totalEpisodeDueCount = media
	.map((anime) => {
		if ($settings.displayTotalEpisodes && !$settings.displayTotalDueEpisodes)
			return 1;

		if (
			$settings.displayTotalDueEpisodes &&
			completed &&
			!$settings.displayTotalEpisodes
		)
			return 1;

		if ($settings.displayTotalEpisodes && anime.status === "FINISHED")
			return anime.episodes - (anime.mediaListEntry?.progress || 0);

		if (anime.status === "NOT_YET_RELEASED") return 1;

		return (
			(anime.nextAiringEpisode?.episode || 1) -
			(anime.mediaListEntry?.progress || 0) -
			(upcoming || notYetReleased ? 0 : 1)
		);
	})
	.reduce((a, b) => a + b, 0);
let lists: string[] = [];
let filterKind = upcoming
	? "Upcoming"
	: notYetReleased
		? "NotYetReleased"
		: completed
			? "Completed"
			: "Due";
const filterKey = `${filterKind}AnimeListFilter`;
let selectedList = "All";

$: {
	const persistedList = disableFilter
		? "All"
		: ($stateBin[filterKey] as string) || "All";

	if (persistedList !== selectedList) selectedList = persistedList;
}

$: lists = Array.from(
	new Set(
		media
			.flatMap((m) => Object.entries(m.mediaListEntry?.customLists ?? {}))
			.filter(([_key, value]) => value)
			.map(([key]) => key),
	),
);

$: hasDistinguishingList = lists.some((list) =>
	media.some((m) => !m.mediaListEntry?.customLists?.[list]),
);

$: if (
	lists.length > 0 &&
	selectedList !== "All" &&
	!lists.includes(selectedList)
) {
	selectedList = "All";

	if (!disableFilter) $stateBin[filterKey] = "All";
}

$: filteredMedia =
	selectedList === "All" || !$settings.displayMediaListFilter
		? media
		: media.filter((m) => m.mediaListEntry?.customLists?.[selectedList]);

$: if (browser && !dummy && media && previousAnimeList !== media)
	previousAnimeList = media;

const updateSelectedList = (event: Event) => {
	const nextSelectedList = (event.currentTarget as HTMLSelectElement).value;

	track("Filter Anime List", {
		scope: nextSelectedList === "All" ? "all" : "custom",
	});

	selectedList = nextSelectedList;

	if (!disableFilter && $stateBin[filterKey] !== nextSelectedList)
		$stateBin[filterKey] = nextSelectedList;
};

const clearAiringRefreshTimeout = () => {
	if (airingRefreshTimeout) clearTimeout(airingRefreshTimeout);

	airingRefreshTimeout = undefined;
	scheduledAiringAt = null;
};

const scheduleAiringRefresh = () => {
	if (!browser) return;

	if (dummy || media.length === 0) {
		clearAiringRefreshTimeout();

		return;
	}

	const nextAiringAt = media.reduce<number | null>((closest, currentMedia) => {
		if (
			currentMedia.status !== "RELEASING" &&
			currentMedia.status !== "NOT_YET_RELEASED"
		)
			return closest;

		const airingAt = currentMedia.nextAiringEpisode?.airingAt;

		if (!airingAt) return closest;
		if (closest === null) return airingAt;

		return airingAt < closest ? airingAt : closest;
	}, null);

	if (!nextAiringAt) {
		clearAiringRefreshTimeout();

		return;
	}

	if (airingRefreshTimeout && scheduledAiringAt === nextAiringAt) return;

	clearAiringRefreshTimeout();
	scheduledAiringAt = nextAiringAt;
	airingRefreshTimeout = setTimeout(
		() => {
			const now = Date.now() / 1000;

			if (
				media.some(
					(m) =>
						m.nextAiringEpisode?.airingAt && m.nextAiringEpisode.airingAt < now,
				)
			)
				$revalidateAnime = $revalidateAnime + 1;

			scheduleAiringRefresh();
		},
		Math.max(1000, nextAiringAt * 1000 - Date.now() + 250),
	);
};

onMount(async () => {
	if (dummy) return;

	scheduleAiringRefresh();

	if (browser)
		await localforage.setItem(
			`last${
				notYetReleased
					? "NotYetReleased"
					: upcoming
						? "Upcoming"
						: completed
							? "Completed"
							: ""
			}AnimeListLength`,
			media.length.toString(),
		);
});

$: if (browser && !dummy) {
	media;

	scheduleAiringRefresh();
}

onDestroy(() => clearAiringRefreshTimeout());

const increment = (anime: Media, progress: number) => {
	if (dummy || pendingUpdate === anime.id) return;

	track("Increment Anime Progress");

	pendingUpdate = anime.id;
	lastUpdatedMedia = anime.id;

	const target = media.find((m) => m.id === anime.id);

	if (target?.mediaListEntry) target.mediaListEntry.progress = progress + 1;

	media =
		!completed && target && !hasDueEpisodes(target)
			? media.filter((m) => m.id !== anime.id)
			: media;

	incrementMediaProgress(anime.id, progress, user, (skipped) => {
		pendingUpdate = null;

		if (!skipped) $revalidateAnime = $revalidateAnime + 1;
	});
};
</script>

<ListTitle
  time={endTime / 1000}
  count={$settings.displayTotalDueEpisodes || $settings.displayTotalEpisodes
    ? totalEpisodeDueCount
    : filteredMedia.length}
  {title}
  hideTime={dummy}
  hideCount={dummy || media.length === 0}
>
  {#if $settings.displayMediaRoulette && !upcoming && !notYetReleased && filteredMedia.length > 1}
    <button
      class="small-button"
      onclick={() => (showRoulette = true)}
      title={$locale().lists.actions?.pickRandomAnime}
      data-umami-event="Open Anime Roulette"
    >
      Roulette
    </button>
  {/if}
</ListTitle>

{#if media.length === 0}
  {$locale().lists.empty?.anime}
  <button
    onclick={() => (animeLists = cleanCache(user, $identity))}
    data-umami-event="Force Refresh Anime"
  >
    {$locale().lists.actions?.forceRefresh}
  </button>
{:else if $settings.displayMediaListFilter && !disableFilter && hasDistinguishingList}
  <select value={selectedList} onchange={updateSelectedList}>
    <option value="All">{$locale().lists.actions?.all}</option>

    {#each lists as list}
      <option value={list}>{list}</option>
    {/each}
  </select>

  <Spacer />
{/if}

{#if $settings.displayCoverModeAnime}
  <CleanGrid
    media={filteredMedia}
    {dummy}
    type="anime"
    {upcoming}
    {notYetReleased}
    reverseSort={$settings.displayReverseSort}
    {limit}
  >
    <div slot="title" let:title={anime} let:progress>
      {#if !upcoming && !notYetReleased}
        {progress}{@html totalEpisodes(anime)}
        <button
          class={`button-square button-action ${pendingUpdate === anime.id ? 'opaque' : ''}`}
          style={pendingUpdate === anime.id ? 'pointer-events: none;' : ''}
          onclick={() => increment(anime, progress)}>+</button
        >
        {#if !completed || dummy}
          [{anime.nextAiringEpisode?.episode === -1
            ? '?'
            : (anime.nextAiringEpisode?.episode || 1) - 1}]
          <br />
          <AiringTime originalAnime={anime} />
        {/if}
      {:else}
        <AiringTime originalAnime={anime} upcoming={true} />
      {/if}
    </div>
  </CleanGrid>
{:else}
  <CleanList
    media={filteredMedia}
    type="anime"
    {upcoming}
    {notYetReleased}
    {lastUpdatedMedia}
    reverseSort={$settings.displayReverseSort}
  >
    <span slot="information" let:title={anime} let:progress>
      {#if !upcoming || notYetReleased || !$settings.displayCountdownRightAligned}
        <span class="opaque">|</span>
      {/if}
      {#if !upcoming || notYetReleased}
        <!-- {anime.mediaListEntry?.progress || 0}{@html totalEpisodes(anime)} -->
        {progress}{@html totalEpisodes(anime)}
        <button
          class={`button-square button-action ${pendingUpdate === anime.id ? 'opaque' : ''}`}
          style={pendingUpdate === anime.id ? 'pointer-events: none;' : ''}
          onclick={() => increment(anime, progress)}>+</button
        >
        {#if !completed}
          [{anime.nextAiringEpisode?.episode === -1
            ? '?'
            : (anime.nextAiringEpisode?.episode || 1) - 1}]
          <span class:countdown={$settings.displayCountdownRightAligned}>
            <AiringTime originalAnime={anime} />
          </span>
        {/if}
      {:else}
        <span class:countdown={$settings.displayCountdownRightAligned}>
          <AiringTime originalAnime={anime} upcoming={true} />
        </span>
      {/if}
    </span>
  </CleanList>
{/if}

{#if showRoulette && !upcoming && !notYetReleased}
  <MediaRoulette media={filteredMedia} type="anime" onClose={() => (showRoulette = false)} />
{/if}
