<script lang="ts">
  import { afterNavigate, goto } from '$app/navigation';
  import { page } from '$app/stores';
  import { intersectionObserver } from '$lib/actions/intersection-observer';
  import { resizeObserver } from '$lib/actions/resize-observer';
  import { shortcuts } from '$lib/actions/shortcut';
  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
  import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
  import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte';
  import ChangeDate from '$lib/components/photos-page/actions/change-date-action.svelte';
  import ChangeLocation from '$lib/components/photos-page/actions/change-location-action.svelte';
  import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
  import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
  import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
  import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
  import TagAction from '$lib/components/photos-page/actions/tag-action.svelte';
  import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
  import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
  import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
  import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
  import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
  import {
    notificationController,
    NotificationType,
  } from '$lib/components/shared-components/notification/notification';
  import { AppRoute, QueryParameter } from '$lib/constants';
  import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
  import { assetViewingStore } from '$lib/stores/asset-viewing.store';
  import { type Viewport } from '$lib/stores/assets-store.svelte';
  import { loadMemories, memoryStore } from '$lib/stores/memory.store';
  import { locale, videoViewerMuted } from '$lib/stores/preferences.store';
  import { preferences } from '$lib/stores/user.store';
  import { getAssetPlaybackUrl, getAssetThumbnailUrl, handlePromiseError, memoryLaneTitle } from '$lib/utils';
  import { cancelMultiselect } from '$lib/utils/asset-utils';
  import { fromLocalDateTime } from '$lib/utils/timeline-util';
  import {
    AssetMediaSize,
    AssetTypeEnum,
    deleteMemory,
    removeMemoryAssets,
    updateMemory,
    type AssetResponseDto,
    type MemoryResponseDto,
  } from '@immich/sdk';
  import { IconButton } from '@immich/ui';
  import {
    mdiCardsOutline,
    mdiChevronDown,
    mdiChevronLeft,
    mdiChevronRight,
    mdiChevronUp,
    mdiDotsVertical,
    mdiHeart,
    mdiHeartOutline,
    mdiImageMinusOutline,
    mdiImageSearch,
    mdiPause,
    mdiPlay,
    mdiPlus,
    mdiSelectAll,
    mdiVolumeOff,
    mdiVolumeHigh,
  } from '@mdi/js';
  import type { NavigationTarget } from '@sveltejs/kit';
  import { DateTime } from 'luxon';
  import { onMount } from 'svelte';
  import { t } from 'svelte-i18n';
  import { tweened } from 'svelte/motion';
  import { derived as storeDerived } from 'svelte/store';
  import { fade } from 'svelte/transition';

  type MemoryIndex = {
    memoryIndex: number;
    assetIndex: number;
  };

  type MemoryAsset = MemoryIndex & {
    memory: MemoryResponseDto;
    asset: AssetResponseDto;
    previousMemory?: MemoryResponseDto;
    previous?: MemoryAsset;
    next?: MemoryAsset;
    nextMemory?: MemoryResponseDto;
  };

  let memoryGallery: HTMLElement | undefined = $state();
  let memoryWrapper: HTMLElement | undefined = $state();
  let galleryInView = $state(false);
  let paused = $state(false);
  let current = $state<MemoryAsset | undefined>(undefined);
  let isSaved = $derived(current?.memory.isSaved);
  let resetPromise = $state(Promise.resolve());

  const { isViewing } = assetViewingStore;
  const viewport: Viewport = $state({ width: 0, height: 0 });
  const assetInteraction = new AssetInteraction();
  let progressBarController = tweened<number>(0, {
    duration: (from: number, to: number) => (to ? 5000 * (to - from) : 0),
  });
  let videoPlayer: HTMLVideoElement | undefined = $state();
  const memories = storeDerived(memoryStore, (memories) => {
    memories = memories ?? [];
    const memoryAssets: MemoryAsset[] = [];
    let previous: MemoryAsset | undefined;
    for (const [memoryIndex, memory] of memories.entries()) {
      for (const [assetIndex, asset] of memory.assets.entries()) {
        const current = {
          memory,
          memoryIndex,
          previousMemory: memories[memoryIndex - 1],
          nextMemory: memories[memoryIndex + 1],
          asset,
          assetIndex,
          previous,
        };

        memoryAssets.push(current);

        if (previous) {
          previous.next = current;
        }

        previous = current;
      }
    }

    return memoryAssets;
  });

  const loadFromParams = (memories: MemoryAsset[], page: typeof $page | NavigationTarget | null) => {
    const assetId = page?.params?.assetId ?? page?.url.searchParams.get(QueryParameter.ID) ?? undefined;
    handlePromiseError(handleAction($isViewing ? 'pause' : 'reset'));
    return memories.find((memory) => memory.asset.id === assetId) ?? memories[0];
  };
  const asHref = (asset: AssetResponseDto) => `?${QueryParameter.ID}=${asset.id}`;
  const handleNavigate = async (asset?: AssetResponseDto) => {
    if ($isViewing) {
      return asset;
    }

    await handleAction('reset');
    if (!asset) {
      return;
    }

    // Adjust the progress bar duration to the video length
    setProgressDuration(asset);

    await goto(asHref(asset));
  };
  const setProgressDuration = (asset: AssetResponseDto) => {
    if (asset.type === AssetTypeEnum.Video) {
      const timeParts = asset.duration.split(':').map(Number);
      const durationInMilliseconds = (timeParts[0] * 3600 + timeParts[1] * 60 + timeParts[2]) * 1000;
      progressBarController = tweened<number>(0, {
        duration: (from: number, to: number) => (to ? durationInMilliseconds * (to - from) : 0),
      });
    } else {
      progressBarController = tweened<number>(0, {
        duration: (from: number, to: number) => (to ? 5000 * (to - from) : 0),
      });
    }
  };
  const handleNextAsset = () => handleNavigate(current?.next?.asset);
  const handlePreviousAsset = () => handleNavigate(current?.previous?.asset);
  const handleNextMemory = () => handleNavigate(current?.nextMemory?.assets[0]);
  const handlePreviousMemory = () => handleNavigate(current?.previousMemory?.assets[0]);
  const handleEscape = async () => goto(AppRoute.PHOTOS);
  const handleSelectAll = () => assetInteraction.selectAssets(current?.memory.assets || []);
  const handleAction = async (action: 'reset' | 'pause' | 'play') => {
    switch (action) {
      case 'play': {
        paused = false;
        await videoPlayer?.play();
        await progressBarController.set(1);
        break;
      }

      case 'pause': {
        paused = true;
        videoPlayer?.pause();
        await progressBarController.set($progressBarController);
        break;
      }

      case 'reset': {
        paused = false;
        videoPlayer?.pause();
        resetPromise = progressBarController.set(0);
        break;
      }
    }
  };
  const handleProgress = async (progress: number) => {
    if (progress === 0 && !paused) {
      await handleAction('play');
      return;
    }

    if (progress === 1) {
      await progressBarController.set(0);
      await (current?.next ? handleNextAsset() : handleAction('pause'));
    }
  };
  const handleUpdate = () => {
    if (!current) {
      return;
    }
    // eslint-disable-next-line no-self-assign
    current.memory.assets = current.memory.assets;
  };

  const handleRemove = (ids: string[]) => {
    if (!current) {
      return;
    }
    const idSet = new Set(ids);
    current.memory.assets = current.memory.assets.filter((asset) => !idSet.has(asset.id));
    init();
  };

  const init = () => {
    $memoryStore = $memoryStore.filter((memory) => memory.assets.length > 0);
    if ($memoryStore.length === 0) {
      return handlePromiseError(goto(AppRoute.PHOTOS));
    }

    current = loadFromParams($memories, $page);

    // Adjust the progress bar duration to the video length
    setProgressDuration(current.asset);
  };

  const handleDeleteMemoryAsset = async (current?: MemoryAsset) => {
    if (!current) {
      return;
    }

    if (current.memory.assets.length === 1) {
      return handleDeleteMemory(current);
    }

    if (current.previous) {
      current.previous.next = current.next;
    }
    if (current.next) {
      current.next.previous = current.previous;
    }

    current.memory.assets = current.memory.assets.filter((asset) => asset.id !== current.asset.id);

    // eslint-disable-next-line no-self-assign
    $memoryStore = $memoryStore;

    await removeMemoryAssets({ id: current.memory.id, bulkIdsDto: { ids: [current.asset.id] } });
  };

  const handleDeleteMemory = async (current?: MemoryAsset) => {
    if (!current) {
      return;
    }

    await deleteMemory({ id: current.memory.id });

    notificationController.show({ message: $t('removed_memory'), type: NotificationType.Info });

    await loadMemories();
    init();
  };

  const handleSaveMemory = async (current?: MemoryAsset) => {
    if (!current) {
      return;
    }

    current.memory.isSaved = !current.memory.isSaved;

    await updateMemory({
      id: current.memory.id,
      memoryUpdateDto: {
        isSaved: current.memory.isSaved,
      },
    });

    notificationController.show({
      message: current.memory.isSaved ? $t('added_to_favorites') : $t('removed_from_favorites'),
      type: NotificationType.Info,
    });
  };

  onMount(async () => {
    if (!$memoryStore) {
      await loadMemories();
    }

    init();
  });

  afterNavigate(({ from, to }) => {
    let target = null;
    if (to?.params?.assetId) {
      target = to;
    } else if (from?.params?.assetId) {
      target = from;
    } else {
      target = $page;
    }

    current = loadFromParams($memories, target);
  });

  $effect(() => {
    handlePromiseError(handleProgress($progressBarController));
  });

  $effect(() => {
    handlePromiseError(handleAction(galleryInView ? 'pause' : 'play'));
  });

  $effect(() => {
    if (videoPlayer) {
      videoPlayer.muted = $videoViewerMuted;
    }
  });
</script>

<svelte:window
  use:shortcuts={$isViewing
    ? []
    : [
        { shortcut: { key: 'ArrowRight' }, onShortcut: () => handleNextAsset() },
        { shortcut: { key: 'd' }, onShortcut: () => handleNextAsset() },
        { shortcut: { key: 'ArrowLeft' }, onShortcut: () => handlePreviousAsset() },
        { shortcut: { key: 'a' }, onShortcut: () => handlePreviousAsset() },
        { shortcut: { key: 'Escape' }, onShortcut: () => handleEscape() },
      ]}
/>

{#if assetInteraction.selectionActive}
  <div class="sticky top-0 z-[90]">
    <AssetSelectControlBar
      assets={assetInteraction.selectedAssets}
      clearSelect={() => cancelMultiselect(assetInteraction)}
    >
      <CreateSharedLink />
      <CircleIconButton title={$t('select_all')} icon={mdiSelectAll} onclick={handleSelectAll} />

      <ButtonContextMenu icon={mdiPlus} title={$t('add_to')}>
        <AddToAlbum />
        <AddToAlbum shared />
      </ButtonContextMenu>

      <FavoriteAction removeFavorite={assetInteraction.isAllFavorite} onFavorite={handleUpdate} />

      <ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
        <DownloadAction menuItem />
        <ChangeDate menuItem />
        <ChangeLocation menuItem />
        <ArchiveAction menuItem unarchive={assetInteraction.isAllArchived} onArchive={handleRemove} />
        {#if $preferences.tags.enabled && assetInteraction.isAllUserOwned}
          <TagAction menuItem />
        {/if}
        <DeleteAssets menuItem onAssetDelete={handleRemove} />
      </ButtonContextMenu>
    </AssetSelectControlBar>
  </div>
{/if}

<section id="memory-viewer" class="w-full bg-immich-dark-gray" bind:this={memoryWrapper}>
  {#if current && current.memory.assets.length > 0}
    <ControlAppBar onClose={() => goto(AppRoute.PHOTOS)} forceDark multiRow>
      {#snippet leading()}
        {#if current}
          <p class="text-lg">
            {$memoryLaneTitle(current.memory)}
          </p>
        {/if}
      {/snippet}

      <div class="flex place-content-center place-items-center gap-2 overflow-hidden">
        <CircleIconButton
          title={paused ? $t('play_memories') : $t('pause_memories')}
          icon={paused ? mdiPlay : mdiPause}
          onclick={() => handleAction(paused ? 'play' : 'pause')}
          class="hover:text-black"
        />

        {#each current.memory.assets as asset, index (asset.id)}
          <a class="relative w-full py-2" href={asHref(asset)}>
            <span class="absolute left-0 h-[2px] w-full bg-gray-500"></span>
            {#await resetPromise}
              <span class="absolute left-0 h-[2px] bg-white" style:width={`${index < current.assetIndex ? 100 : 0}%`}
              ></span>
            {:then}
              <span
                class="absolute left-0 h-[2px] bg-white"
                style:width={`${index < current.assetIndex ? 100 : index > current.assetIndex ? 0 : $progressBarController * 100}%`}
              ></span>
            {/await}
          </a>
        {/each}

        <div>
          <p class="text-small">
            {(current.assetIndex + 1).toLocaleString($locale)}/{current.memory.assets.length.toLocaleString($locale)}
          </p>
        </div>
        <CircleIconButton
          title={$videoViewerMuted ? $t('unmute_memories') : $t('mute_memories')}
          icon={$videoViewerMuted ? mdiVolumeOff : mdiVolumeHigh}
          onclick={() => ($videoViewerMuted = !$videoViewerMuted)}
        />
      </div>
    </ControlAppBar>

    {#if galleryInView}
      <div
        class="fixed top-20 z-30 left-1/2 -translate-x-1/2 transition-opacity"
        class:opacity-0={!galleryInView}
        class:opacity-100={galleryInView}
      >
        <button
          type="button"
          onclick={() => memoryWrapper?.scrollIntoView({ behavior: 'smooth' })}
          disabled={!galleryInView}
        >
          <CircleIconButton title={$t('hide_gallery')} icon={mdiChevronUp} color="light" onclick={() => {}} />
        </button>
      </div>
    {/if}
    <!-- Viewer -->
    <section class="overflow-hidden pt-32 md:pt-20">
      <div
        class="ml-[-100%] box-border flex h-[calc(100vh_-_224px)] md:h-[calc(100vh_-_180px)] w-[300%] items-center justify-center gap-10 overflow-hidden"
      >
        <!-- PREVIOUS MEMORY -->
        <div class="h-1/2 w-[20vw] rounded-2xl {current.previousMemory ? 'opacity-25 hover:opacity-70' : 'opacity-0'}">
          <button
            type="button"
            class="relative h-full w-full rounded-2xl"
            disabled={!current.previousMemory}
            onclick={handlePreviousMemory}
          >
            {#if current.previousMemory && current.previousMemory.assets.length > 0}
              <img
                class="h-full w-full rounded-2xl object-cover"
                src={getAssetThumbnailUrl({ id: current.previousMemory.assets[0].id, size: AssetMediaSize.Preview })}
                alt={$t('previous_memory')}
                draggable="false"
              />
            {:else}
              <enhanced:img
                class="h-full w-full rounded-2xl object-cover"
                src="$lib/assets/no-thumbnail.png"
                sizes="min(271px,186px)"
                alt={$t('previous_memory')}
                draggable="false"
              />
            {/if}

            {#if current.previousMemory}
              <div class="absolute bottom-4 right-4 text-left text-white">
                <p class="text-xs font-semibold text-gray-200">{$t('previous').toUpperCase()}</p>
                <p class="text-xl">{$memoryLaneTitle(current.previousMemory)}</p>
              </div>
            {/if}
          </button>
        </div>

        <!-- CURRENT MEMORY -->
        <div
          class="main-view relative flex h-full w-[70vw] place-content-center place-items-center rounded-2xl bg-black"
        >
          <div class="relative h-full w-full rounded-2xl bg-black">
            {#key current.asset.id}
              <div transition:fade class="h-full w-full">
                {#if current.asset.type == AssetTypeEnum.Video}
                  <video
                    bind:this={videoPlayer}
                    autoplay
                    playsinline
                    class="h-full w-full rounded-2xl object-contain transition-all"
                    src={getAssetPlaybackUrl({ id: current.asset.id })}
                    poster={getAssetThumbnailUrl({ id: current.asset.id, size: AssetMediaSize.Preview })}
                    draggable="false"
                    muted={$videoViewerMuted}
                    transition:fade
                  ></video>
                {:else}
                  <img
                    class="h-full w-full rounded-2xl object-contain transition-all"
                    src={getAssetThumbnailUrl({ id: current.asset.id, size: AssetMediaSize.Preview })}
                    alt={current.asset.exifInfo?.description}
                    draggable="false"
                    transition:fade
                  />
                {/if}
              </div>
            {/key}

            <div
              class="absolute bottom-0 right-0 p-2 transition-all flex h-full justify-between flex-col items-end gap-2"
              class:opacity-0={galleryInView}
              class:opacity-100={!galleryInView}
            >
              <div class="flex">
                <IconButton
                  icon={isSaved ? mdiHeart : mdiHeartOutline}
                  shape="round"
                  variant="ghost"
                  size="giant"
                  color="secondary"
                  aria-label={isSaved ? $t('unfavorite') : $t('favorite')}
                  onclick={() => handleSaveMemory(current)}
                  class="text-white dark:text-white"
                />
                <!-- <IconButton
                  icon={mdiShareVariantOutline}
                  shape="round"
                  variant="ghost"
                  size="giant"
                  color="secondary"
                  aria-label={$t('share')}
                /> -->
                <ButtonContextMenu
                  icon={mdiDotsVertical}
                  title={$t('menu')}
                  onclick={() => handleAction('pause')}
                  direction="left"
                  align="bottom-right"
                  class="text-white dark:text-white"
                >
                  <MenuOption onClick={() => handleDeleteMemory(current)} text="Remove memory" icon={mdiCardsOutline} />
                  <MenuOption
                    onClick={() => handleDeleteMemoryAsset(current)}
                    text="Remove photo from this memory"
                    icon={mdiImageMinusOutline}
                  />
                  <!-- shortcut={{ key: 'l', shift: shared }} -->
                </ButtonContextMenu>
              </div>

              <div>
                <IconButton
                  href="{AppRoute.PHOTOS}?at={current.asset.id}"
                  icon={mdiImageSearch}
                  aria-label={$t('view_in_timeline')}
                  color="secondary"
                  variant="ghost"
                  shape="round"
                  size="giant"
                  class="text-white dark:text-white"
                />
              </div>
            </div>
            <!-- CONTROL BUTTONS -->
            {#if current.previous}
              <div class="absolute top-1/2 left-0 ml-4">
                <CircleIconButton
                  title={$t('previous_memory')}
                  icon={mdiChevronLeft}
                  color="dark"
                  onclick={handlePreviousAsset}
                />
              </div>
            {/if}

            {#if current.next}
              <div class="absolute top-1/2 right-0 mr-4">
                <CircleIconButton
                  title={$t('next_memory')}
                  icon={mdiChevronRight}
                  color="dark"
                  onclick={handleNextAsset}
                />
              </div>
            {/if}

            <div class="absolute left-8 top-4 text-sm font-medium text-white">
              <p>
                {fromLocalDateTime(current.memory.assets[0].localDateTime).toLocaleString(DateTime.DATE_FULL)}
              </p>
              <p>
                {current.asset.exifInfo?.city || ''}
                {current.asset.exifInfo?.country || ''}
              </p>
            </div>
          </div>
        </div>

        <!-- NEXT MEMORY -->
        <div class="h-1/2 w-[20vw] rounded-2xl {current.nextMemory ? 'opacity-25 hover:opacity-70' : 'opacity-0'}">
          <button
            type="button"
            class="relative h-full w-full rounded-2xl"
            onclick={handleNextMemory}
            disabled={!current.nextMemory}
          >
            {#if current.nextMemory && current.nextMemory.assets.length > 0}
              <img
                class="h-full w-full rounded-2xl object-cover"
                src={getAssetThumbnailUrl({ id: current.nextMemory.assets[0].id, size: AssetMediaSize.Preview })}
                alt={$t('next_memory')}
                draggable="false"
              />
            {:else}
              <enhanced:img
                class="h-full w-full rounded-2xl object-cover"
                src="$lib/assets/no-thumbnail.png"
                sizes="min(271px,186px)"
                alt={$t('next_memory')}
                draggable="false"
              />
            {/if}

            {#if current.nextMemory}
              <div class="absolute bottom-4 left-4 text-left text-white">
                <p class="text-xs font-semibold text-gray-200">{$t('up_next').toUpperCase()}</p>
                <p class="text-xl">{$memoryLaneTitle(current.nextMemory)}</p>
              </div>
            {/if}
          </button>
        </div>
      </div>
    </section>

    <!-- GALLERY VIEWER -->
    <section class="bg-immich-dark-gray p-4">
      <div
        class="sticky mb-10 flex place-content-center place-items-center transition-all"
        class:opacity-0={galleryInView}
        class:opacity-100={!galleryInView}
      >
        <CircleIconButton
          title={$t('show_gallery')}
          icon={mdiChevronDown}
          color="light"
          onclick={() => memoryGallery?.scrollIntoView({ behavior: 'smooth' })}
        />
      </div>

      <div
        id="gallery-memory"
        use:intersectionObserver={{
          onIntersect: () => (galleryInView = true),
          onSeparate: () => (galleryInView = false),
          bottom: '-200px',
        }}
        use:resizeObserver={({ height, width }) => ((viewport.height = height), (viewport.width = width))}
        bind:this={memoryGallery}
      >
        <GalleryViewer
          onNext={handleNextAsset}
          onPrevious={handlePreviousAsset}
          assets={current.memory.assets}
          {viewport}
          {assetInteraction}
        />
      </div>
    </section>
  {/if}
</section>

<style>
  .main-view {
    box-shadow:
      0 4px 4px 0 rgba(0, 0, 0, 0.3),
      0 8px 12px 6px rgba(0, 0, 0, 0.15);
  }
</style>
