<!--
  One tile of a mosaic: artwork at an exact pixel box, with its label written
  over the bottom of the image rather than beneath it.

  The label lives on the artwork on purpose — a caption below would add height
  outside the box the layout computed, and a row whose captions wrap to two
  lines would no longer line up with its neighbours. Keeping everything inside
  the box is what lets `layoutMosaic` own the geometry completely.

  TRACES: UR-075 | DR-174
-->
<script lang="ts">
  import type { Snippet } from "svelte";
  import CachedImage from "$lib/components/common/CachedImage.svelte";

  interface Props {
    label: string;
    width: number;
    height: number;
    /** Item whose Primary image is the artwork. Omit for an icon-only tile. */
    itemId?: string;
    imageTag?: string | null;
    /** Drawn instead of artwork — favourites tiles have no image of their own. */
    icon?: Snippet;
    /** Tints an icon-only tile so it reads as a destination, not a broken image. */
    accent?: boolean;
    onclick?: () => void;
    /**
     * Reports the artwork's true aspect ratio once decoded, so the grid can
     * re-pack against the shape the image actually has.
     */
    onRatio?: (ratio: number) => void;
  }

  let {
    label,
    width,
    height,
    itemId,
    imageTag,
    icon,
    accent = false,
    onclick,
    onRatio,
  }: Props = $props();

  // Request an image comfortably larger than the box so a wide tile is not
  // upscaled, without refetching every time the container resizes (CachedImage
  // keys its fetch on the item, not on this number).
  const REQUEST_WIDTH = 480;
</script>

<button
  type="button"
  {onclick}
  aria-label={label}
  class="group/tile relative overflow-hidden rounded-lg bg-[var(--color-surface)] shadow-md
         transition-transform duration-200 hover:z-10 hover:scale-[1.03] hover:shadow-2xl
         focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-jellyfin)]"
  style="width: {width}px; height: {height}px;"
>
  {#if icon}
    <div
      class="absolute inset-0 flex items-center justify-center
             {accent
        ? 'bg-gradient-to-br from-[var(--color-jellyfin)]/40 to-[var(--color-jellyfin)]/5'
        : 'bg-[var(--color-surface)]'}"
    >
      {@render icon()}
    </div>
  {:else if itemId}
    <CachedImage
      {itemId}
      imageType="Primary"
      tag={imageTag}
      maxWidth={REQUEST_WIDTH}
      alt={label}
      class="absolute inset-0 w-full h-full object-cover transition-transform duration-300 group-hover/tile:scale-105"
      onNaturalSize={(w, h) => onRatio?.(w / h)}
    />
  {/if}

  <!-- Legibility wash: only as tall as the caption needs, so artwork stays
       artwork. -->
  <div class="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/85 via-black/45 to-transparent pt-6 pb-2 px-2.5">
    <p class="truncate text-left text-sm font-semibold text-white drop-shadow group-hover/tile:text-[var(--color-jellyfin)] transition-colors">
      {label}
    </p>
  </div>
</button>
