<!--
  Mark an episode, season or series watched — or unwatched again.

  The backend already had both halves (`mark_played` / `clear_watch_history`,
  both recursive over a container on the server) and the sync queue already
  replayed the first; nothing in the UI had ever called them, so the only way to
  mark something watched was to sit through it. This is that control.

  Unlike ClearHistoryButton — which is the *destructive* "erase all history for
  this series", confirms, and needs the server — this is an everyday toggle: no
  confirmation, and it works offline by queueing, in both directions.

  TRACES: UR-073 | DR-158
-->
<script lang="ts">
  import { syncService } from "$lib/services/syncService";
  import { createLogger } from "$lib/utils/logger";

  const log = createLogger("WatchedToggleButton");

  interface Props {
    /** Episode, season or series id. */
    itemId: string;
    /** Current watched state, as the caller knows it. */
    watched: boolean;
    /** What is being marked, for the tooltip wording. */
    scope: "episode" | "season" | "series";
    size?: "sm" | "lg";
    /** Show a text label beside the icon rather than icon-only. */
    showLabel?: boolean;
    /** Called after a successful toggle so the caller can reload. */
    onChanged?: (watched: boolean) => void;
  }

  let {
    itemId,
    watched,
    scope,
    size = "lg",
    showLabel = false,
    onChanged,
  }: Props = $props();

  let busy = $state(false);

  // Optimistic state: the caller's `watched` prop only catches up once it has
  // reloaded from the repository, which on a season means a round trip. Without
  // this the button visibly ignores the first tap.
  let optimistic = $state<boolean | null>(null);
  const isWatched = $derived(optimistic ?? watched);

  // A new item in the same slot (scrolling a virtualised list, switching series)
  // must drop the previous item's optimistic state or it shows the wrong tick.
  $effect(() => {
    // Bare read: registers `itemId` as a dependency of this effect. Svelte 5
    // idiom, not a stray expression.
    // eslint-disable-next-line @typescript-eslint/no-unused-expressions
    itemId;
    optimistic = null;
  });

  const subject = $derived(
    scope === "series" ? "series" : scope === "season" ? "season" : "episode"
  );
  const label = $derived(isWatched ? "Watched" : "Mark watched");
  const title = $derived(
    isWatched
      ? `Mark this ${subject} unwatched`
      : scope === "episode"
        ? "Mark this episode watched"
        : `Mark every episode in this ${subject} watched`
  );

  async function handleClick() {
    if (busy) return;

    const next = !isWatched;
    busy = true;
    optimistic = next;
    try {
      if (next) {
        await syncService.queueMarkPlayed(itemId);
      } else {
        await syncService.queueMarkUnplayed(itemId);
      }
      onChanged?.(next);
    } catch (e) {
      // Put the button back where it was — the change did not happen.
      optimistic = null;
      log.error("Failed to change watched state:", e);
    } finally {
      busy = false;
    }
  }
</script>

<button
  type="button"
  onclick={handleClick}
  disabled={busy}
  {title}
  aria-label={title}
  aria-pressed={isWatched}
  class="rounded-lg font-medium flex items-center gap-2 transition-colors
         disabled:opacity-40 disabled:cursor-not-allowed
         {isWatched
    ? 'bg-[var(--color-jellyfin)]/15 text-[var(--color-jellyfin)] hover:bg-[var(--color-jellyfin)]/25'
    : 'bg-[var(--color-surface)] text-gray-300 hover:bg-[var(--color-surface-hover)] hover:text-white'}
         {showLabel ? (size === 'lg' ? 'px-6 py-2' : 'px-3 py-1.5 text-sm') : size === 'lg' ? 'p-2' : 'p-1.5'}"
>
  {#if busy}
    <div
      class="border-2 border-current border-t-transparent rounded-full animate-spin
             {size === 'lg' ? 'w-5 h-5' : 'w-4 h-4'}"
    ></div>
  {:else if isWatched}
    <!-- Filled check: this one is done. -->
    <svg
      class={size === "lg" ? "w-5 h-5" : "w-4 h-4"}
      fill="currentColor"
      viewBox="0 0 24 24"
      aria-hidden="true"
    >
      <path
        d="M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm-1.4 14.6L6 12l1.4-1.4 3.2 3.2
           6.4-6.4L18.4 8.8l-7.8 7.8z"
      />
    </svg>
  {:else}
    <!-- Outline check: available, not yet done. -->
    <svg
      class={size === "lg" ? "w-5 h-5" : "w-4 h-4"}
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      viewBox="0 0 24 24"
      aria-hidden="true"
    >
      <circle cx="12" cy="12" r="9" />
      <path stroke-linecap="round" stroke-linejoin="round" d="M8 12.5l2.5 2.5L16 9.5" />
    </svg>
  {/if}
  {#if showLabel}
    <span>{busy ? "Saving…" : label}</span>
  {/if}
</button>
