<script lang="ts">
  import { page } from '$app/state';
  import { goto } from '$app/navigation';
  import { untrack } from 'svelte';
  import { pageTitle } from '$lib/branding';
  import { Pencil, Trash2, Link as LinkIcon } from 'lucide-svelte';
  import { useQueryClient } from '@tanstack/svelte-query';
  import PlaylistTrackRow from '$lib/components/PlaylistTrackRow.svelte';
  import ApiErrorBanner from '$lib/components/ApiErrorBanner.svelte';
  import {
    createPlaylistQuery,
    updatePlaylist,
    deletePlaylist,
    removePlaylistTrack,
    reorderPlaylist,
    refreshSystem,
    systemShuffle
  } from '$lib/api/playlists';
  import { qk } from '$lib/api/queries';
  import { user } from '$lib/auth/store.svelte';
  import { errCode, errMessage } from '$lib/api/errors';
  import { playQueue } from '$lib/player/store.svelte';
  import { systemPlaylistRefetch } from '$lib/playlists/systemRefetch';
  import { systemRebuilt } from '$lib/serverEvents.svelte';
  import { pushToast } from '$lib/stores/toast.svelte';
  import type { TrackRef } from '$lib/api/types';

  // SvelteKit types page.params.id as string | undefined (the route
  // could in principle render with the slot empty); empty-string fallback
  // keeps every downstream call typed and falls back to a 404 if the
  // operator ever lands here without a real id.
  const id = $derived(page.params.id ?? '');
  const queryClient = useQueryClient();
  const playlistStore = $derived(createPlaylistQuery(id));
  const playlistQuery = $derived($playlistStore);
  const isOwner = $derived(user.value?.id === playlistQuery?.data?.user_id);

  async function onMove(fromPos: number, toPos: number) {
    if (!playlistQuery?.data) return;
    const tracks = playlistQuery.data.tracks;
    // Clamp toPos so a drag past the list edges resolves cleanly to the
    // first/last slot rather than producing an out-of-range index.
    const clamped = Math.max(0, Math.min(tracks.length - 1, toPos));
    if (fromPos === clamped) return;
    const cur = tracks.map((t) => t.position);
    const moved = cur.splice(fromPos, 1)[0];
    cur.splice(clamped, 0, moved);
    try {
      await reorderPlaylist(id, cur);
      await queryClient.invalidateQueries({ queryKey: qk.playlist(id) });
    } catch (e: unknown) {
      alert(errMessage(e));
    }
  }

  async function onRemove(position: number) {
    try {
      await removePlaylistTrack(id, position);
      await queryClient.invalidateQueries({ queryKey: qk.playlist(id) });
      await queryClient.invalidateQueries({ queryKey: qk.playlists() });
    } catch (e: unknown) {
      alert(errMessage(e));
    }
  }

  function onPlay(position: number) {
    if (!playlistQuery?.data) return;
    const data = playlistQuery.data;
    // Skip rows where track_id is null (track was removed from library).
    const live = data.tracks.filter((t) => t.track_id !== null).map(toTrackRef);
    // Find which `live` index corresponds to the clicked position. Some
    // positions in the original list may be unavailable (filtered out),
    // so the index in the playable list != the playlist position.
    const clickedTrackID = data.tracks.find((t) => t.position === position)?.track_id;
    const startIdx = live.findIndex((t) => t.id === clickedTrackID);
    // #968: a system playlist played from its detail page is tagged with its
    // source (so play_started advances the rotation, matching the home tile and
    // the Android detail screen) and gets the self-heal closure so a stale
    // snapshot re-pulls on total failure.
    const variant = data.system_variant;
    if (variant != null) {
      playQueue(live, Math.max(0, startIdx), {
        source: variant,
        refetch: systemPlaylistRefetch({
          variant,
          playlistId: id,
          perArtist: data.seed_artist_id != null
        })
      });
    } else {
      playQueue(live, Math.max(0, startIdx));
    }
  }

  function toTrackRef(t: {
    track_id: string | null;
    album_id: string | null;
    artist_id: string | null;
    title: string;
    album_title: string;
    artist_name: string;
    duration_sec: number;
    stream_url: string | null;
  }): TrackRef {
    return {
      id: t.track_id!,
      title: t.title,
      album_id: t.album_id ?? '',
      album_title: t.album_title,
      artist_id: t.artist_id ?? '',
      artist_name: t.artist_name,
      duration_sec: t.duration_sec,
      stream_url: t.stream_url ?? ''
    } as unknown as TrackRef;
  }

  let editing = $state(false);
  let editName = $state('');
  let editDesc = $state('');
  let editIsPublic = $state(false);

  function startEdit() {
    if (!playlistQuery?.data) return;
    editName = playlistQuery.data.name;
    editDesc = playlistQuery.data.description;
    editIsPublic = playlistQuery.data.is_public;
    editing = true;
  }

  async function submitEdit() {
    try {
      await updatePlaylist(id, { name: editName, description: editDesc, is_public: editIsPublic });
      await queryClient.invalidateQueries({ queryKey: qk.playlist(id) });
      await queryClient.invalidateQueries({ queryKey: qk.playlists() });
      editing = false;
    } catch (e: unknown) {
      alert(errMessage(e));
    }
  }

  async function submitDelete() {
    if (!confirm('Delete this playlist? This cannot be undone.')) return;
    try {
      await deletePlaylist(id);
      await queryClient.invalidateQueries({ queryKey: qk.playlists() });
      goto('/playlists');
    } catch (e: unknown) {
      alert(errMessage(e));
    }
  }

  async function onCopyLink() {
    const href = `${window.location.origin}/playlists/${id}`;
    try {
      await navigator.clipboard.writeText(href);
      pushToast('Link copied to clipboard.');
    } catch {
      // navigator.clipboard fails in insecure contexts and older browsers.
      // Fall back to selecting the link in a prompt so the user can copy.
      window.prompt('Copy the playlist link:', href);
    }
  }

  // --- System-playlist refresh (#411 R2: generic by-kind) ---
  let refreshingSystem = $state(false);

  async function onRefreshSystem(variant: string) {
    refreshingSystem = true;
    try {
      await refreshSystem(variant);
      pushToast('Refreshed.');
      await queryClient.invalidateQueries({ queryKey: qk.playlist(id) });
      await queryClient.invalidateQueries({ queryKey: qk.playlists() });
    } catch (e: unknown) {
      pushToast(`Refresh failed: ${errCode(e)}`, 'error');
    } finally {
      refreshingSystem = false;
    }
  }

  // --- #980: stale-view banner for an open system-playlist detail page ---
  // The daily rebuild rotates the playlist id, so this page's cached data goes
  // stale and can't be refetched in place (the old id 404s). Watch the global
  // rebuild counter; when it advances while we're on a system playlist, offer a
  // Refresh that re-resolves the variant to the fresh playlist.
  const isSystemPlaylist = $derived(playlistQuery?.data?.system_variant != null);
  // Baseline starts at the current count so a rebuild earlier this session
  // doesn't flash the banner on open; re-synced on navigation below.
  let acknowledgedRebuild = $state(systemRebuilt.count);
  $effect(() => {
    id; // re-sync the baseline whenever we land on a different playlist
    acknowledgedRebuild = untrack(() => systemRebuilt.count);
  });
  const staleSystemView = $derived(
    isSystemPlaylist && systemRebuilt.count > acknowledgedRebuild
  );

  let reloadingStale = $state(false);
  async function onReloadStale() {
    const variant = playlistQuery?.data?.system_variant;
    if (!variant) return;
    reloadingStale = true;
    try {
      // The rebuild minted a new playlist id for this variant; resolve it and
      // navigate there so the page loads the fresh mix.
      const fresh = await systemShuffle(variant);
      await goto(`/playlists/${fresh.id}`);
    } catch (e: unknown) {
      pushToast(`Couldn’t load the refreshed mix: ${errCode(e)}`, 'error');
    } finally {
      reloadingStale = false;
    }
  }
</script>

<svelte:head>
  <title>{pageTitle(playlistQuery?.data?.name ? `Playlist · ${playlistQuery.data.name}` : 'Playlist')}</title>
</svelte:head>

<div class="mx-auto max-w-4xl px-4 py-6">
  {#if playlistQuery?.isPending}
    <p class="text-text-muted">Loading…</p>
  {:else if playlistQuery?.isError}
    <ApiErrorBanner error={playlistQuery.error} onRetry={() => playlistQuery.refetch()} />
  {:else if playlistQuery?.data}
    {@const pl = playlistQuery.data}
    {#if staleSystemView}
      <div
        role="status"
        class="mb-4 flex items-center justify-between gap-3 rounded-md border border-accent/40
               bg-accent/10 px-4 py-2.5 text-sm"
      >
        <span class="text-text-primary">This mix was refreshed since you opened it.</span>
        <button
          type="button"
          onclick={onReloadStale}
          disabled={reloadingStale}
          class="flex-shrink-0 rounded-md bg-action-secondary px-3 py-1 text-xs font-medium
                 text-action-fg disabled:opacity-50"
        >
          {reloadingStale ? 'Refreshing…' : 'Refresh'}
        </button>
      </div>
    {/if}
    {#if !editing}
      <header class="mb-6 flex items-start gap-4">
        <div class="h-32 w-32 flex-shrink-0 overflow-hidden rounded-md bg-surface-hover">
          {#if pl.cover_url}
            <img src={pl.cover_url} alt="" class="h-full w-full object-cover" />
          {/if}
        </div>
        <div class="min-w-0 flex-1">
          <h1 class="truncate text-2xl font-medium text-text-primary">{pl.name}</h1>
          {#if pl.description}
            <p class="mt-1 text-sm text-text-muted">{pl.description}</p>
          {/if}
          <p class="mt-2 text-xs text-text-muted">
            {pl.track_count} {pl.track_count === 1 ? 'track' : 'tracks'}
            {#if pl.is_public}· public{:else}· private{/if}
            {#if !isOwner}· by {pl.owner_username}{/if}
          </p>
        </div>
        {#if pl.refreshable && pl.system_variant}
          <button
            type="button"
            disabled={refreshingSystem}
            onclick={() => onRefreshSystem(pl.system_variant as string)}
            class="rounded-md border border-border px-3 py-1.5 text-sm text-text-muted hover:text-text-primary"
            aria-label="Refresh playlist"
          >
            {refreshingSystem ? 'Refreshing…' : 'Refresh'}
          </button>
        {/if}
        {#if isOwner && pl.is_public}
          <button
            type="button"
            onclick={onCopyLink}
            aria-label="Copy public link to this playlist"
            title="Copy link"
            class="rounded p-2 text-text-muted hover:bg-surface-hover hover:text-text-primary"
          >
            <LinkIcon size={16} strokeWidth={1} />
          </button>
        {/if}
        {#if isOwner}
          <button
            type="button"
            onclick={startEdit}
            aria-label="Edit playlist"
            class="rounded p-2 text-text-muted hover:bg-surface-hover hover:text-text-primary"
          >
            <Pencil size={16} strokeWidth={1} />
          </button>
          <button
            type="button"
            onclick={submitDelete}
            aria-label="Delete playlist"
            class="rounded p-2 text-text-muted hover:bg-surface-hover hover:text-action-destructive"
          >
            <Trash2 size={16} strokeWidth={1} />
          </button>
        {/if}
      </header>
    {:else}
      <section class="mb-6 rounded-md border border-border bg-surface p-4">
        <label class="block text-sm font-medium text-text-primary">
          Name
          <input
            type="text"
            bind:value={editName}
            class="mt-1 w-full rounded border border-border bg-background px-3 py-1.5 text-sm text-text-primary"
          />
        </label>
        <label class="mt-3 block text-sm font-medium text-text-primary">
          Description
          <textarea
            bind:value={editDesc}
            rows="2"
            class="mt-1 w-full rounded border border-border bg-background px-3 py-1.5 text-sm text-text-primary"
          ></textarea>
        </label>
        <label class="mt-3 flex items-center gap-2 text-sm text-text-primary">
          <input type="checkbox" bind:checked={editIsPublic} class="rounded border-border" />
          Make this playlist public
        </label>
        <div class="mt-3 flex justify-end gap-2">
          <button
            type="button"
            onclick={() => (editing = false)}
            class="rounded-md border border-border px-3 py-1.5 text-sm text-text-muted hover:text-text-primary"
          >
            Cancel
          </button>
          <button
            type="button"
            onclick={submitEdit}
            class="rounded-md bg-action-secondary px-3 py-1.5 text-sm text-action-fg"
          >
            Save
          </button>
        </div>
      </section>
    {/if}

    <section class="rounded-md border border-border bg-surface">
      {#if pl.tracks.length === 0}
        <p class="px-3 py-6 text-sm text-text-muted">
          {#if isOwner}
            No tracks yet. Add some via the "Add to playlist…" entry on any track row.
          {:else}
            No tracks in this playlist.
          {/if}
        </p>
      {:else}
        {#each pl.tracks as row (row.position)}
          <PlaylistTrackRow
            {row}
            isOwner={isOwner ?? false}
            onRemove={onRemove}
            onPlay={onPlay}
            onMove={onMove}
          />
        {/each}
      {/if}
    </section>
  {/if}
</div>
