<script lang="ts">
  import { tick } from 'svelte';
  import HasToolAccess from '$lib/components/HasToolAccess.svelte';
  import ScoreBar from '$lib/components/ScoreBar.svelte';
  import PlaylistSongCard from './PlaylistSongCard.svelte';
  import type { MapDetailWithOrder, MapDetail, MapVersion, MapDifficulty, PlaylistFull } from '$lib/server/beatsaver';
  import type { BeatLeaderPlayerProfile } from '$lib/utils/plebsaber-utils';
  import { TOOL_REQUIREMENTS } from '$lib/utils/plebsaber-utils';

  const SORT_OPTIONS = ['Latest', 'Relevance', 'Curated'] as const;
  type SortOrderOption = (typeof SORT_OPTIONS)[number];
  type SearchState = {
    submitted: boolean;
    query: string;
    sortOrder: SortOrderOption;
    curated: boolean;
    verified: boolean;
  };

  type PlaylistWithStats = PlaylistFull & { stats?: PlaylistFull['stats'] };

  type PlaylistSongCardData = {
    id: string;
    hash: string;
    coverURL?: string;
    songName: string;
    mapper?: string;
    timeset?: number;
    diffName: string;
    modeName: string;
    beatsaverKey?: string;
    publishedLabel: string;
    score?: number | null;
  };

  type PlaylistState = {
    loading: boolean;
    maps: PlaylistSongCardData[];
    error: string | null;
    total: number | null;
    offset: number;
  };

  const SONGS_PER_PAGE = 12;

  export let data: {
    playlists: PlaylistWithStats[];
    page: number;
    totalPages: number | null;
    total: number | null;
    pageSize: number;
    error: string | null;
    player: BeatLeaderPlayerProfile | null;
    adminRank: number | null;
    adminPlayer: BeatLeaderPlayerProfile | null;
    search: SearchState;
  };

  const requirement = TOOL_REQUIREMENTS['playlist-discovery'];

  const playlistBaseUrl = 'https://beatsaver.com/playlists/';
  const profileBaseUrl = 'https://beatsaver.com/profile/';

  let searchForm: HTMLFormElement | null = null;
  let appliedSearch: SearchState = {
    submitted: false,
    query: '',
    sortOrder: 'Latest',
    curated: false,
    verified: true
  };
  let searchQuery = appliedSearch.query;
  let sortOrder: SortOrderOption = appliedSearch.sortOrder;
  let curated = appliedSearch.curated;
  let verified = appliedSearch.verified;
  let lastSearchKey = '';

  $: appliedSearch = {
    submitted: data?.search?.submitted ?? false,
    query: data?.search?.query ?? '',
    sortOrder: (data?.search?.sortOrder ?? 'Latest') as SortOrderOption,
    curated: Boolean(data?.search?.curated),
    verified: data?.search?.verified ?? true
  };

  $: if (data) {
    searchQuery = appliedSearch.query;
    sortOrder = appliedSearch.sortOrder;
    curated = appliedSearch.curated;
    verified = appliedSearch.verified;
  }

  $: playlists = data?.playlists ?? [];
  $: page = data?.page ?? 1;
  $: totalPages = data?.totalPages ?? null;
  $: total = data?.total ?? null;
  $: pageSize = data?.pageSize ?? 0;

  $: hasPrev = page > 1;
  $: hasNext = totalPages != null ? page < totalPages : playlists.length > 0 && playlists.length === pageSize;
  $: prevHref = hasPrev ? buildPageHref(page - 1) : null;
  $: nextHref = hasNext ? buildPageHref(page + 1) : null;

  let expanded: Record<number, boolean> = {};
  let playlistState: Record<number, PlaylistState> = {};

  $: {
    const currentKey = JSON.stringify({
      page,
      query: appliedSearch.query,
      sortOrder: appliedSearch.sortOrder,
      curated: appliedSearch.curated,
      verified: appliedSearch.verified
    });
    if (currentKey !== lastSearchKey) {
      lastSearchKey = currentKey;
      expanded = {};
      playlistState = {};
    }
  }

  type PresetKey = 'default' | 'picks' | 'packs';

  function buildPageHref(targetPage: number): string {
    const params = new URLSearchParams();
    params.set('page', String(targetPage));
    params.set('submitted', '1');
    if (appliedSearch.query) params.set('q', appliedSearch.query);
    if (appliedSearch.sortOrder) params.set('order', appliedSearch.sortOrder);
    if (appliedSearch.curated) params.set('curated', 'on');
    if (appliedSearch.verified) params.set('verified', 'on');
    return `?${params.toString()}`;
  }

  async function submitSearch() {
    await tick();
    searchForm?.requestSubmit();
  }

  async function applyPreset(preset: PresetKey) {
    switch (preset) {
      case 'default':
        searchQuery = '';
        sortOrder = 'Latest';
        curated = false;
        verified = true;
        break;
      case 'picks':
        searchQuery = 'picks';
        sortOrder = 'Latest';
        curated = false;
        verified = false;
        break;
      case 'packs':
        searchQuery = 'pack';
        sortOrder = 'Latest';
        curated = false;
        verified = true;
        break;
    }

    await submitSearch();
  }

function togglePlaylist(id: number) {
  const currentlyExpanded = Boolean(expanded[id]);
  const nextExpanded: Record<number, boolean> = {};
  if (!currentlyExpanded) {
    nextExpanded[id] = true;
  }
  expanded = nextExpanded;
  if (!currentlyExpanded) {
    const state = playlistState[id];
    const currentOffset = state && typeof state.offset === 'number' ? state.offset : 0;
    loadPlaylistMaps(id, currentOffset);
  }
}

  async function loadPlaylistMaps(id: number, offset = 0) {
    playlistState = {
      ...playlistState,
      [id]: { loading: true, maps: playlistState[id]?.maps ?? [], error: null, total: playlistState[id]?.total ?? null, offset }
    };
    try {
      const res = await fetch(`/tools/playlist-discovery/playlist?id=${encodeURIComponent(String(id))}&offset=${offset}`);
      if (!res.ok) throw new Error(`Failed to load playlist ${id}: ${res.status}`);
      const payload = (await res.json()) as { maps?: MapDetailWithOrder[] | null; totalCount?: number | null; offset: number; pageSize?: number };
      const maps = Array.isArray(payload?.maps) ? payload.maps : [];
      const cards = maps
        .map((entry) => mapEntryToCard(entry))
        .filter((card): card is PlaylistSongCardData => card !== null);
      playlistState = {
        ...playlistState,
        [id]: {
          loading: false,
          maps: cards,
          error: null,
          total:
            typeof payload?.totalCount === 'number'
              ? payload.totalCount
              : playlistState[id]?.total ?? (offset + cards.length),
          offset: payload?.offset ?? offset
        }
      };
    } catch (err) {
      playlistState = {
        ...playlistState,
        [id]: { loading: false, maps: [], error: err instanceof Error ? err.message : 'Failed to load playlist songs', total: null, offset }
      };
    }
  }

  function getPublishedVersion(map: MapDetail | undefined | null) {
    const versions = map?.versions ?? [];
    return versions.find((v) => v.state === 'Published') ?? versions[0];
  }

  function getPrimaryDifficulty(version: MapVersion | undefined | null): MapDifficulty | null {
    const diffs = version?.diffs ?? [];
    return diffs.length > 0 ? diffs[0] : null;
  }

  function toEpochSeconds(value: string | undefined | null): number | undefined {
    if (!value) return undefined;
    const ts = Date.parse(value);
    if (Number.isNaN(ts)) return undefined;
    return Math.floor(ts / 1000);
  }

  function formatDate(value: string | undefined | null): string {
    if (!value) return '';
    const date = new Date(value);
    if (Number.isNaN(date.getTime())) return '';
    return date.toLocaleDateString();
  }

  function mapEntryToCard(entry: MapDetailWithOrder): PlaylistSongCardData | null {
    const rawMap = (entry as unknown as { map?: MapDetail }).map ?? (entry as unknown as MapDetail);
    if (!rawMap) return null;

    const versions = rawMap.versions ?? [];
    const version = getPublishedVersion(rawMap) ?? versions[0] ?? null;
    const diff = version ? getPrimaryDifficulty(version) : null;
    const published = rawMap.lastPublishedAt ?? rawMap.createdAt ?? rawMap.updatedAt ?? version?.createdAt ?? null;
    const hash = version?.hash ?? rawMap.id ?? rawMap.hash ?? null;
    if (!hash) return null;

    const beatsaverKey = version?.key ?? rawMap.id ?? undefined;
    const coverURL = version?.coverURL ?? rawMap.coverURL ?? undefined;
    const diffName = diff?.difficulty ?? 'Unknown';
    const modeName = diff?.characteristic ?? 'Standard';

    return {
      id: rawMap.id ?? hash,
      hash,
      coverURL,
      songName: rawMap.name,
      mapper: rawMap.uploader?.name ?? undefined,
      timeset: toEpochSeconds(published),
      diffName,
      modeName,
      beatsaverKey,
      publishedLabel: formatDate(published),
      score: typeof rawMap.stats?.score === 'number' ? rawMap.stats.score : null
    };
  }

  function isFiniteScore(value: number | undefined | null): value is number {
    return typeof value === 'number' && Number.isFinite(value);
  }

  function playlistLink(id: number | string | undefined): string {
    if (id === undefined || id === null) return playlistBaseUrl;
    return `${playlistBaseUrl}${id}`;
  }

  function profileLink(id: number | string | undefined): string {
    if (id === undefined || id === null) return profileBaseUrl;
    return `${profileBaseUrl}${id}#playlists`;
  }

  function sanitizeDescription(value: string | null | undefined): string {
    if (!value) return '';
    return value.replace(/[^a-zA-Z0-9 ]+/g, ' ').replace(/\s+/g, ' ').trim();
  }

  function truncateTitle(value: string | null | undefined, maxLength = 20): string {
    if (!value) return '';
    if (value.length <= maxLength) return value;
    const sliced = value.slice(0, maxLength - 1).trimEnd();
    return `${sliced}…`;
  }
</script>

<section class="py-8">
  <h1 class="font-display text-3xl sm:text-4xl">Playlist Discovery</h1>
  <p class="mt-2 text-muted max-w-2xl">
    This page is a UI/UX experiment that shows playlists with a reactive interface for previewing map ratings and listening to song previews.
    See <strong><a href="https://bsaber.com/playlists" target="_blank" rel="noreferrer">Featured Packs</a> on Beast Saber</strong> for curated map packs.
  </p>

  <HasToolAccess player={data?.player ?? null} requirement={requirement} adminRank={data?.adminRank ?? null} adminPlayer={data?.adminPlayer ?? null}>
    <form class="search-form card-surface" method="GET" bind:this={searchForm}>
      <input type="hidden" name="submitted" value="1" />
      <input type="hidden" name="page" value="1" />

      <div class="search-grid">
        <label class="form-field search-query">
          <span class="form-label">Search</span>
          <input type="search" name="q" placeholder="Search playlists" bind:value={searchQuery} />
        </label>
        <label class="form-field sort-field">
          <span class="form-label">Sort order</span>
          <select name="order" bind:value={sortOrder} on:change={submitSearch}>
            {#each SORT_OPTIONS as option}
              <option value={option}>{option}</option>
            {/each}
          </select>
        </label>
      </div>

      <div class="search-filters">
        <label class="filter-checkbox">
          <input type="checkbox" name="curated" value="on" bind:checked={curated} on:change={submitSearch} />
          <span>Curated playlists</span>
        </label>
        <label class="filter-checkbox">
          <input type="checkbox" name="verified" value="on" bind:checked={verified} on:change={submitSearch} />
          <span>Playlists from verified mappers</span>
        </label>
      </div>

      <div class="search-actions">
        <button class="form-btn primary" type="submit">Search</button>
        <div class="preset-group">
          <span class="preset-label">Presets:</span>
          <button class="form-btn secondary" type="button" on:click={() => applyPreset('packs')}>
            Map Packs
          </button>
          <button class="form-btn secondary" type="button" on:click={() => applyPreset('picks')}>
            Picks
          </button>
        </div>
      </div>
    </form>

    {#if data?.error}
      <div class="mt-6 rounded-md border border-danger/40 bg-danger/10 px-4 py-3 text-danger text-sm">
        {data.error}
      </div>
    {/if}

    {#if !data?.error && playlists.length === 0}
      <div class="mt-6 text-muted">No playlists found for this page.</div>
    {/if}

    {#if playlists.length > 0}
      <div class="mt-6 flex flex-wrap items-center gap-3 text-sm text-muted">
        <span>Page {page} {#if totalPages}of {totalPages}{/if}</span>
        {#if total != null}
          <span>({total.toLocaleString()} total playlists)</span>
        {/if}
      </div>

      <div class="mt-6 playlists-grid">
        {#each playlists as playlist (playlist.playlistId)}
          {@const displayName = truncateTitle(playlist.name)}
          {@const description = sanitizeDescription(playlist.description ?? '')}
          <article class:expanded={Boolean(expanded[playlist.playlistId])} class="playlist-tile card-surface">
            <button class="tile-header" type="button" on:click={() => togglePlaylist(playlist.playlistId)}>
              <div class="tile-cover">
                <img src={playlist.playlistImage} alt={`Playlist cover for ${playlist.name}`} loading="lazy" />
              </div>
              <div class="tile-main">
                <div class="tile-title">
                  <a
                    href={playlistLink(playlist.playlistId)}
                    target="_blank"
                    rel="noopener noreferrer"
                    on:click|stopPropagation
                    title={playlist.name}
                  >
                    {displayName}
                  </a>
                  <span class="map-count">{playlist.stats?.totalMaps ?? 0} maps</span>
                </div>
                {#if isFiniteScore(playlist.stats?.avgScore)}
                  <div class="tile-score">
                    <ScoreBar value={playlist.stats?.avgScore ?? null} size="sm" />
                  </div>
                {/if}
                <div class="tile-sub">
                  <span>
                    by
                    {#if playlist.owner?.name}
                      <a href={profileLink(playlist.owner.id)} target="_blank" rel="noopener noreferrer" on:click|stopPropagation>
                        {playlist.owner.name}
                      </a>
                    {:else}
                      Unknown curator
                    {/if}
                  </span>
                </div>
              </div>
              <div class:arrow-expanded={Boolean(expanded[playlist.playlistId])} class="tile-arrow" aria-hidden="true">
                <svg viewBox="0 0 24 24" width="20" height="20" role="presentation">
                  <path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
                </svg>
              </div>
            </button>

            {#if expanded[playlist.playlistId]}
              {@const state = playlistState[playlist.playlistId]}
              {@const offset = state?.offset ?? 0}
              {@const total = typeof state?.total === 'number' ? state.total : null}
              {@const currentCount = state?.maps?.length ?? 0}
              {@const atFirstPage = offset <= 0}
              {@const atLastPage = total !== null
                ? (offset + currentCount) >= total
                : currentCount < SONGS_PER_PAGE}
              <div class="tile-body">
                {#if description}
                  <div class="tile-description">{description}</div>
                {/if}
                {#if state?.loading}
                  <div class="tile-status">Loading songs…</div>
                {:else if state?.error}
                  <div class="tile-status error">{state.error}</div>
                {:else if state?.maps?.length}
                  {#if state.total && state.total > SONGS_PER_PAGE}
                    <div class="tile-pagination">
                      <button
                        class="pager-btn"
                        on:click={() => loadPlaylistMaps(playlist.playlistId, Math.max(0, offset - SONGS_PER_PAGE))}
                        disabled={state.loading || atFirstPage}
                      >
                        Prev {SONGS_PER_PAGE}
                      </button>
                      <span class="tile-status">
                        {#if state.total === 0}
                          No songs
                        {:else}
                          Showing {(state.offset ?? 0) + 1}
                          -
                          {Math.min(
                            (state.offset ?? 0) + state.maps.length,
                            state.total ?? 0
                          )}
                          of {state.total}
                        {/if}
                      </span>
                      <button
                        class="pager-btn"
                        on:click={() => loadPlaylistMaps(playlist.playlistId, offset + SONGS_PER_PAGE)}
                        disabled={state.loading || atLastPage}
                      >
                        Next {SONGS_PER_PAGE}
                      </button>
                    </div>
                  {/if}
                  <div class="songs-grid">
                    {#each state.maps as card (card.id)}
                      <PlaylistSongCard
                        hash={card.hash}
                        coverURL={card.coverURL}
                        songName={card.songName}
                        mapper={card.mapper}
                        beatsaverKey={card.beatsaverKey}
                        score={card.score ?? null}
                      />
                    {/each}
                  </div>
                {:else}
                  <div class="tile-status">No songs found.</div>
                {/if}
              </div>
            {/if}
          </article>
        {/each}
      </div>

      <div class="mt-8 flex items-center justify-center gap-3">
        <a class="pager-btn" href={prevHref} aria-disabled={!hasPrev} data-disabled={!hasPrev}>
          Prev
        </a>
        <span class="text-sm text-muted">Page {page}{#if totalPages} / {totalPages}{/if}</span>
        <a class="pager-btn" href={nextHref} aria-disabled={!hasNext} data-disabled={!hasNext}>
          Next
        </a>
      </div>
    {/if}
  </HasToolAccess>
</section>

<style>
  .search-form {
    margin-top: 1.75rem;
    width: 100%;
    max-width: 600px;
    padding: 1.25rem 1.5rem;
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
  }

  .search-grid {
    display: grid;
    gap: 1rem;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
  }

  .form-field {
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
  }

  .form-label {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: rgba(148, 163, 184, 0.75);
  }

  .search-query {
    min-width: 0;
  }

  .sort-field {
    max-width: 10em;
  }

  .form-field input,
  .form-field select {
    background: rgba(15, 23, 42, 0.6);
    border: 1px solid rgba(148, 163, 184, 0.2);
    border-radius: 0.5rem;
    padding: 0.55rem 0.75rem;
    color: #e2e8f0;
    font-size: 0.95rem;
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
  }

  .form-field input::placeholder {
    color: rgba(148, 163, 184, 0.6);
  }

  .form-field input:focus,
  .form-field select:focus {
    outline: none;
    border-color: rgba(94, 234, 212, 0.5);
    box-shadow: 0 0 0 2px rgba(94, 234, 212, 0.2);
  }

  .search-filters {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem 1rem;
    align-items: stretch;
  }

  .filter-checkbox {
    position: relative;
    display: inline-flex;
    align-items: center;
    font-size: 0.9rem;
    color: rgba(226, 232, 240, 0.9);
    line-height: 1.45;
    padding: 0.55rem 0.9rem 0.55rem 2.35rem;
    border: 1px solid rgba(148, 163, 184, 0.2);
    border-radius: 0.5rem;
    background: rgba(15, 23, 42, 0.6);
    transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
    cursor: pointer;
  }

  .filter-checkbox:hover {
    border-color: rgba(94, 234, 212, 0.35);
    background: rgba(15, 23, 42, 0.7);
  }

  .filter-checkbox:focus-within {
    border-color: rgba(94, 234, 212, 0.5);
    box-shadow: 0 0 0 2px rgba(94, 234, 212, 0.2);
  }

  .filter-checkbox input {
    position: absolute;
    left: 0.75rem;
    top: 50%;
    transform: translateY(-50%);
    flex-shrink: 0;
  }

  .filter-checkbox span {
    display: inline-block;
  }

  .search-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem 1.5rem;
    align-items: center;
  }

  .form-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.5rem 1.25rem;
    border-radius: 0.5rem;
    border: 1px solid rgba(148, 163, 184, 0.25);
    background: rgba(15, 23, 42, 0.5);
    color: white;
    font-size: 0.9rem;
    font-weight: 500;
    transition: all 0.2s ease;
    cursor: pointer;
  }

  .form-btn:hover {
    border-color: rgba(94, 234, 212, 0.5);
    box-shadow: 0 0 12px rgba(94, 234, 212, 0.2);
  }

  .form-btn.primary {
    background: rgba(94, 234, 212, 0.85);
    border-color: rgba(94, 234, 212, 0.85);
    color: #0f172a;
    font-weight: 600;
  }

  .form-btn.primary:hover {
    box-shadow: 0 8px 20px rgba(94, 234, 212, 0.25);
  }

  .form-btn.secondary {
    background: rgba(15, 23, 42, 0.45);
  }

  .form-btn:disabled {
    opacity: 0.45;
    cursor: default;
    box-shadow: none;
    border-color: rgba(148, 163, 184, 0.2);
  }

  .preset-group {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
  }

  .preset-label {
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: rgba(148, 163, 184, 0.75);
  }

  @media (max-width: 640px) {
    .search-form {
      padding: 1rem;
    }

    .search-grid {
      grid-template-columns: minmax(0, 1fr);
    }

    .sort-field {
      max-width: none;
    }

    .search-filters {
      flex-direction: column;
      align-items: stretch;
    }

    .search-actions {
      flex-direction: column;
      align-items: stretch;
      gap: 0.9rem;
    }

    .preset-group {
      justify-content: flex-start;
    }
  }

  .playlists-grid {
    display: grid;
    gap: 1.25rem;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  }

  .playlist-tile {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    transition: transform 0.2s ease, box-shadow 0.2s ease;
  }

  .playlist-tile.expanded {
    grid-column: 1 / -1;
  }

  .tile-header {
    width: 100%;
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: 1rem;
    padding: 1rem 1.25rem 0.85rem;
    background: transparent;
    color: inherit;
    border: none;
    cursor: pointer;
    text-align: left;
  }

  .tile-cover {
    position: relative;
    width: 88px;
    height: 88px;
    border-radius: 0.6rem;
    overflow: hidden;
    background: rgba(15, 23, 42, 0.6);
  }

  .tile-cover img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
  }

  .tile-main {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }

  .tile-title {
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
  }

  .tile-title a {
    color: white;
    font-size: 1.05rem;
    font-weight: 600;
    text-decoration: none;
  }

  .tile-title a:hover {
    text-decoration: underline;
  }

  .map-count {
    font-size: 0.85rem;
    color: rgba(148, 163, 184, 0.85);
  }

  .tile-score {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
  }

  .tile-score :global(.score-meter) {
    width: 100%;
    max-width: 280px;
  }

  .tile-sub {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    font-size: 0.9rem;
    color: rgba(148, 163, 184, 0.95);
  }

  .tile-sub a {
    color: var(--color-neon);
    text-decoration: none;
  }

  .tile-sub a:hover {
    text-decoration: underline;
    color: color-mix(in srgb, var(--color-neon) 80%, white 20%);
  }

  .tile-arrow {
    align-self: center;
    justify-self: center;
    color: rgba(148, 163, 184, 0.75);
    transition: transform 0.2s ease, color 0.2s ease;
  }

  .tile-arrow.arrow-expanded {
    transform: rotate(180deg);
    color: var(--color-neon);
  }

  .tile-body {
    border-top: 1px solid rgba(148, 163, 184, 0.08);
    padding: 1rem 1.25rem 1.25rem;
  }

  .tile-description {
    font-size: 0.95rem;
    line-height: 1.45;
    color: rgba(226, 232, 240, 0.85);
    background: rgba(15, 23, 42, 0.55);
    padding: 0.75rem 0.9rem;
    border-radius: 0.65rem;
    margin-bottom: 0.9rem;
  }

  .tile-status {
    color: rgba(148, 163, 184, 0.95);
    font-size: 0.9rem;
  }

  @media (max-width: 640px) {
    .tile-header {
      grid-template-columns: minmax(64px, 1fr);
      grid-template-rows: auto auto;
      justify-items: stretch;
    }

    .tile-cover {
      width: 100%;
      height: auto;
      aspect-ratio: 16 / 9;
    }

    .tile-arrow {
      justify-self: end;
    }
  }

  .tile-status.error {
    color: #f87171;
  }

  .tile-pagination {
    margin-bottom: 0.5rem;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
  }

  .songs-grid {
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  @media (min-width: 960px) {
    .songs-grid {
      grid-template-columns: repeat(4, minmax(0, 1fr));
    }
  }

  @media (min-width: 1600px) {
    .songs-grid {
      grid-template-columns: repeat(6, minmax(0, 1fr));
    }
  }

  .pager-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.4rem 1.1rem;
    border-radius: 0.375rem;
    border: 1px solid rgba(148, 163, 184, 0.2);
    color: white;
    font-size: 0.9rem;
    text-decoration: none;
    transition: all 0.2s ease;
  }

  .pager-btn:hover {
    border-color: rgba(94, 234, 212, 0.5);
    box-shadow: 0 0 12px rgba(94, 234, 212, 0.2);
  }

  .pager-btn[data-disabled='true'] {
    opacity: 0.45;
    pointer-events: none;
  }

  .pager-btn:disabled,
  .pager-btn:disabled:hover {
    opacity: 0.45;
    pointer-events: none;
    border-color: rgba(148, 163, 184, 0.2);
    box-shadow: none;
    cursor: default;
  }

  .card-surface {
    background: rgba(15, 23, 42, 0.4);
    border: 1px solid rgba(148, 163, 184, 0.08);
    border-radius: 0.75rem;
    overflow: hidden;
  }

  .text-danger {
    color: #f87171;
  }

  .bg-danger\/10 {
    background-color: rgba(248, 113, 113, 0.1);
  }

  .border-danger\/40 {
    border-color: rgba(248, 113, 113, 0.4);
  }
</style>


