<script lang="ts">
  import { type Post, SongThumbnailImageKind } from '$lib/mastodon/response';
  import type { SongInfo } from '$lib/odesliResponse';
  import AvatarComponent from '$lib/components/AvatarComponent.svelte';
  import AccountComponent from '$lib/components/AccountComponent.svelte';
  import { secondsSince, relativeTime } from '$lib/relativeTime';
  import { onMount } from 'svelte';

  interface Props {
    post: Post;
    lazyLoadImages: Boolean;
  }

  let { post, lazyLoadImages = true }: Props = $props();
  let displayRelativeTime = $state(false);
  const absoluteDate = new Date(post.created_at).toLocaleString();
  const timePassed = secondsSince(new Date(post.created_at));
  let dateCreated = $derived.by(() => {
    if (displayRelativeTime) {
      return relativeTime($timePassed) ?? absoluteDate;
    }
    return absoluteDate;
  });
  let loadingProp = $derived(lazyLoadImages ? 'lazy' : 'eager');

  const songs = filterDuplicates(post.songs ?? []);

  function filterDuplicates(songs: SongInfo[]): SongInfo[] {
    return songs.filter((obj, index, arr) => {
      return arr.map((mapObj) => mapObj.pageUrl).indexOf(obj.pageUrl) === index;
    });
  }

  function getThumbnailSize(song: SongInfo): {
    width?: number;
    height?: number;
    widthSmall?: number;
    heightSmall?: number;
  } {
    if (song.thumbnailWidth === undefined || song.thumbnailHeight === undefined) {
      return { width: undefined, height: undefined, widthSmall: undefined, heightSmall: undefined };
    }
    const factor = 200 / song.thumbnailWidth;
    const smallFactor = 60 / song.thumbnailHeight;
    const height = song.thumbnailHeight * factor;
    return {
      width: 200,
      height: height,
      widthSmall: smallFactor * song.thumbnailWidth,
      heightSmall: 60
    };
  }

  // Blurred thumbs aren't generated (yet, unclear of they ever will)
  // So blurred forces using the small one, by skipping the others and removing its media query.
  // This is technically unnecessary - the blurred one will only show if it matches the small media query,
  // but this makes it more explicit
  function getSourceSetHtml(song: SongInfo, isBlurred = false): string {
    const small = new Map<string, string[]>();
    const large = new Map<string, string[]>();

    // Sort thumbnails by file type. This is important, because the order of the srcset entries matter.
    // We need the best format to be first
    const formatPriority = new Map<string, number>([
      ['avif', 0],
      ['webp', 1],
      ['jpg', 99],
      ['jpeg', 99]
    ]);
    const thumbs = (song.resizedThumbnails ?? []).toSorted((a, b) => {
      const extensionA = a.file.split('.').pop() ?? '';
      const extensionB = b.file.split('.').pop() ?? '';
      const prioA = formatPriority.get(extensionA) ?? 3;
      const prioB = formatPriority.get(extensionB) ?? 3;
      return prioA - prioB;
    });

    for (const resizedThumb of thumbs) {
      if (isBlurred && resizedThumb.kind !== SongThumbnailImageKind.Small) {
        continue;
      }
      const extension = resizedThumb.file.split('.').pop();
      const mime = extension ? `image/${extension}` : 'application/octet-stream';
      const sourceSetEntry = `${resizedThumb.file} ${resizedThumb.sizeDescriptor}`;
      switch (resizedThumb.kind) {
        case SongThumbnailImageKind.Big:
          large.set(mime, [...(large.get(mime) || []), sourceSetEntry]);
          break;
        case SongThumbnailImageKind.Small:
          small.set(mime, [...(small.get(mime) || []), sourceSetEntry]);
          break;
        case SongThumbnailImageKind.Blurred: // currently not generated
          break;
      }
    }
    let html = '';
    const { width, height, widthSmall, heightSmall } = getThumbnailSize(song);
    const mediaAttribute = isBlurred ? '' : 'media="(max-width: 650px)"';
    for (const entry of small.entries()) {
      const srcset = entry[1].join(', ');
      html += `<source srcset="${srcset}" type="${entry[0]}" ${mediaAttribute} width="${widthSmall}" height="${heightSmall}" />`;
    }
    html += '\n';
    for (const entry of large.entries()) {
      const srcset = entry[1].join(', ');
      html += `<source srcset="${srcset}" type="${entry[0]}" width="${width}" height="${height}"/>`;
    }
    return html;
  }

  onMount(() => {
    // Display relative time only after mount:
    // When JS is disabled the server-side rendered absolute date will be shown,
    // otherwise the relative date would be outdated very quickly
    displayRelativeTime = true;
  });
</script>

<div class="wrapper">
  <div class="avatar"><AvatarComponent account={post.account} {lazyLoadImages} /></div>
  <div class="account"><AccountComponent account={post.account} /></div>
  <div class="meta">
    <small><a href={post.url} target="_blank" title={absoluteDate}>{dateCreated}</a></small>
  </div>
  <div class="content">{@html post.content}</div>
  <div class="song">
    {#if post.songs}
      {#each songs as song (song.pageUrl)}
        <div class="info-wrapper">
          <picture>
            {@html getSourceSetHtml(song)}
            <img
              class="bgimage"
              src={song.thumbnailUrl}
              loading={loadingProp}
              alt="Blurred cover"
            />
          </picture>
          <a href={song.pageUrl ?? song.postedUrl} target="_blank">
            <div class="info">
              <picture class="cover">
                {@html getSourceSetHtml(song)}
                <img
                  src={song.thumbnailUrl}
                  alt="Cover for {song.artistName} - {song.title}"
                  loading={loadingProp}
                  width={song.thumbnailWidth}
                  height={song.thumbnailHeight}
                />
              </picture>
              <span class="text">{song.artistName} - {song.title}</span>
            </div>
          </a>
        </div>
      {/each}
    {/if}
  </div>
</div>

<style>
  .wrapper {
    display: grid;
    grid-template-columns: 50px 1fr auto auto;
    grid-template-rows: auto 1fr auto;
    grid-template-areas:
      'avatar account account meta'
      'avatar content content song'
      '. content content song';
    grid-column-gap: 6px;
    column-gap: 6px;
    grid-row-gap: 6px;
    row-gap: 6px;
  }
  .avatar {
    grid-area: avatar;
    max-width: 50px;
    max-height: 50px;
  }
  .account {
    grid-area: account;
  }
  .meta {
    grid-area: meta;
    justify-self: end;
  }
  .content {
    grid-area: content;
    word-break: break-word;
    translate: 0 -0.5em;
  }
  .song {
    grid-area: song;
    align-self: center;
    justify-self: center;
    max-width: 200px;
  }
  .cover {
    max-width: 200px;
    display: block;
    border-radius: 3px;
    margin-bottom: 3px;
  }
  .cover img {
    max-width: 200px;
    max-height: 200px;
    object-fit: contain;
    border-radius: 3px;
  }
  .bgimage {
    display: none;
    background-color: var(--color-bg);
  }
  .info {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5em;
    z-index: 1;
  }
  .info * {
    z-index: inherit;
  }
  @media only screen and (max-width: 650px) {
    .wrapper {
      grid-template-areas:
        'avatar account account meta'
        'content content content content'
        'song song song song';
      grid-row-gap: 3px;
      row-gap: 3px;
    }
    .song {
      width: 100%;
    }
    .song,
    .cover {
      max-width: 100%;
    }
    .cover {
      height: 60px;
    }
    .cover:not(.background) {
      z-index: 1;
      backdrop-filter: blur(10px);
      -webkit-backdrop-filter: blur(10px);
    }
    .cover img {
      max-width: 60px;
      max-height: 60px;
    }
    .bgimage {
      display: block;
      width: 100%;
      height: 60px;
      z-index: 0;
      filter: blur(10px);
      background-repeat: no-repeat;
      background-size: cover;
      background-position: center;
    }
    .info {
      position: relative;
      top: -60px;
      flex-direction: row;
    }
    .info-wrapper {
      margin-bottom: -50px;
    }
    .text {
      padding: 3px;
      backdrop-filter: blur(10px);
      -webkit-backdrop-filter: blur(10px);
      border-radius: 3px;
      background-color: var(--color-bg-translucent);
      color: var(--color-text);
    }
  }
</style>
