<script lang="ts">
  import { onMount } from "svelte";
  import { page } from "$app/stores";
  import { truncateMiddle } from "$lib/utils/truncateMiddle";
  import { goto } from "$app/navigation";
  import { navigateUp } from "$lib/utils/navigation";
  import { currentLibrary } from "$lib/stores/library";
  import { auth } from "$lib/stores/auth";
  import SearchBar from "$lib/components/common/SearchBar.svelte";
  import BackButton from "$lib/components/common/BackButton.svelte";
  import ResultsCounter from "$lib/components/common/ResultsCounter.svelte";
  import CachedImage from "$lib/components/common/CachedImage.svelte";
  import { useServerReachabilityReload } from "$lib/composables/useServerReachabilityReload";
  import { useOfflineFilterReload } from "$lib/composables/useOfflineFilterReload";
  import type { Genre, MediaItem, ItemType } from "$lib/api/types";
  import { createLogger } from "$lib/utils/logger";

  const log = createLogger("GenericGenreBrowser");

  /**
   * Generic genre browser supporting Movies, Music Albums, and TV Series
   * Consolidates duplicate genre-browsing logic across media types
   *
   * @req: UR-007 - Navigate media in library
   * @req: UR-030 - Quick genre browsing and filtering
   * @req: DR-007 - Library browsing screens (genre filtering)
   */

  export interface GenreConfig {
    itemTypes: ItemType[]; // ["Movie"] or ["MusicAlbum"] or ["Series"]
    title: string; // "Movie Genres" or "Genres" or "TV Genres"
    backPath: string; // "/library" or "/library/music"
    genreIcon: string; // SVG path for genre icon
    itemDisplayMode: "poster" | "square"; // Aspect ratio: 2/3 or 1/1
    searchPlaceholder?: string; // Optional custom placeholder
    noItemsMessage?: string; // Optional custom empty state
  }

  interface Props {
    config: GenreConfig;
    /**
     * Suppress the back button + title when this renders as a *tab* of a
     * library page that already has a header. Drilling into a single genre
     * still shows the header — there the back button is the way out.
     * TRACES: UR-063 | DR-105
     */
    showHeader?: boolean;
  }

  let { config, showHeader = true }: Props = $props();

  let genres = $state<Genre[]>([]);
  let filteredGenres = $state<Genre[]>([]);
  let loading = $state(true);
  let searchQuery = $state("");
  let selectedGenre = $state<Genre | null>(null);
  let genreItems = $state<MediaItem[]>([]);
  let loadingItems = $state(false);
  async function reloadGenreBrowse() {
    await loadGenres();
    if (selectedGenre) {
      await loadGenreItems(selectedGenre);
    }
  }

  const { markLoaded } = useServerReachabilityReload(reloadGenreBrowse);
  // Re-query when the offline downloaded-only gate changes. TRACES: UR-052 | DR-143
  useOfflineFilterReload(reloadGenreBrowse);

  onMount(async () => {
    await loadGenres();
    // Auto-select a genre when linked with ?genre=<name> (e.g. from a genre tag)
    const requestedGenre = $page.url.searchParams.get("genre");
    if (requestedGenre) {
      const match = genres.find(
        (g) => g.name.toLowerCase() === requestedGenre.toLowerCase(),
      );
      if (match) {
        await loadGenreItems(match);
      }
    }
    markLoaded();
  });

  async function loadGenres() {
    if (!$currentLibrary) {
      goto(config.backPath);
      return;
    }

    try {
      loading = true;
      const repo = auth.getRepository();
      const result = await repo.getGenres($currentLibrary.id);
      genres = result.sort((a, b) => a.name.localeCompare(b.name));
      applyFilter();
    } catch (e) {
      log.error("Failed to load genres:", e);
    } finally {
      loading = false;
    }
  }

  async function loadGenreItems(genre: Genre) {
    if (!$currentLibrary) return;

    try {
      loadingItems = true;
      selectedGenre = genre;
      const repo = auth.getRepository();
      const result = await repo.getItems($currentLibrary.id, {
        includeItemTypes: config.itemTypes,
        genres: [genre.name],
        sortBy: "SortName",
        sortOrder: "Ascending",
        recursive: true,
        limit: 10000,
      });
      genreItems = result.items;
    } catch (e) {
      log.error("Failed to load genre items:", e);
    } finally {
      loadingItems = false;
    }
  }

  function applyFilter() {
    let result = [...genres];

    if (searchQuery.trim()) {
      const query = searchQuery.toLowerCase();
      result = result.filter((genre) => genre.name.toLowerCase().includes(query));
    }

    filteredGenres = result;
  }

  function handleSearch(query: string) {
    searchQuery = query;
    applyFilter();
  }

  function handleGenreClick(genre: Genre) {
    loadGenreItems(genre);
  }

  function handleItemClick(item: MediaItem) {
    goto(`/library/${item.id}`);
  }

  function goBack() {
    if (selectedGenre) {
      selectedGenre = null;
      genreItems = [];
    } else {
      navigateUp(config.backPath);
    }
  }

  const aspectRatioClass = $derived(config.itemDisplayMode === "poster" ? "aspect-[2/3]" : "aspect-square");
  const gridColsClass = $derived(
    config.itemDisplayMode === "poster"
      ? "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5"
      : "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6"
  );
  const searchPlaceholder = $derived(config.searchPlaceholder || `Search ${config.title.toLowerCase()}...`);
  const noItemsMessage = $derived(config.noItemsMessage || `No ${config.itemTypes[0]?.toLowerCase() || "items"} found in this genre`);
</script>

<div class="space-y-6">
  <!-- Header. Inside a genre the back button is the only way out, so it shows
       even when the host page suppresses the top-level header. -->
  {#if showHeader || selectedGenre}
    <div class="flex items-center gap-4">
      <BackButton onClick={goBack} label="Back" />
      <h1 class="text-3xl font-bold text-white">
        {#if selectedGenre}
          {selectedGenre.name}
        {:else}
          {config.title}
        {/if}
      </h1>
    </div>
  {/if}

  {#if !selectedGenre}
    <!-- Genre Browser -->
    <SearchBar value={searchQuery} placeholder={searchPlaceholder} onInput={handleSearch} />

    {#if !loading && filteredGenres.length > 0}
      <ResultsCounter count={filteredGenres.length} itemType="genre" searchQuery={searchQuery} />
    {/if}

    <!-- Genres Grid -->
    {#if loading}
      <div class="grid {gridColsClass} gap-4">
        {#each Array(12) as _}
          <div class="animate-pulse">
            <div class="aspect-square bg-[var(--color-surface)] rounded-lg"></div>
            <div class="mt-2 h-4 bg-[var(--color-surface)] rounded w-3/4"></div>
          </div>
        {/each}
      </div>
    {:else if filteredGenres.length === 0}
      <div class="text-center py-12 text-gray-400">
        <p>No genres found</p>
      </div>
    {:else}
      <div class="grid {gridColsClass} gap-4">
        {#each filteredGenres as genre (genre.id)}
          <button onclick={() => handleGenreClick(genre)} class="group text-left">
            <div
              class="aspect-square bg-gradient-to-br from-[var(--color-jellyfin)]/20 to-[var(--color-jellyfin)]/5 rounded-lg flex items-center justify-center group-hover:from-[var(--color-jellyfin)]/30 group-hover:to-[var(--color-jellyfin)]/10 transition-all"
            >
              <svg
                class="w-12 h-12 text-[var(--color-jellyfin)] opacity-70 group-hover:opacity-100 transition-opacity"
                fill="currentColor"
                viewBox="0 0 24 24"
              >
                {@html config.genreIcon}
              </svg>
            </div>
            <p class="mt-2 text-sm font-medium text-white truncate group-hover:text-[var(--color-jellyfin)] transition-colors">
              {genre.name}
            </p>
          </button>
        {/each}
      </div>
    {/if}
  {:else}
    <!-- Genre Items View -->
    {#if loadingItems}
      <div class="grid {gridColsClass} gap-4">
        {#each Array(10) as _}
          <div class="animate-pulse">
            <div class="{aspectRatioClass} bg-[var(--color-surface)] rounded-lg"></div>
            <div class="mt-2 h-4 bg-[var(--color-surface)] rounded w-3/4"></div>
          </div>
        {/each}
      </div>
    {:else if genreItems.length === 0}
      <div class="text-center py-12 text-gray-400">
        <p>{noItemsMessage}</p>
      </div>
    {:else}
      <div>
        <ResultsCounter count={genreItems.length} itemType={config.itemTypes[0]?.toLowerCase() || "item"} />
        <div class="grid {gridColsClass} gap-4 mt-4">
          {#each genreItems as item (item.id)}
            <button onclick={() => handleItemClick(item)} class="group text-left">
              <div class="{aspectRatioClass} bg-[var(--color-surface)] rounded-lg overflow-hidden mb-2">
                <CachedImage
                  itemId={item.id}
                  imageType="Primary"
                  tag={item.imageId}
                  maxWidth={300}
                  alt={item.name}
                  class="w-full h-full object-cover group-hover:scale-105 transition-transform"
                />
              </div>
              <p class="font-medium text-white truncate group-hover:text-[var(--color-jellyfin)] transition-colors">
                {truncateMiddle(item.name, 40)}
              </p>
              {#if item.productionYear}
                <p class="text-sm text-gray-400">
                  {item.productionYear}
                  {#if item.communityRating}
                    <span class="text-yellow-500 ml-1">★ {item.communityRating.toFixed(1)}</span>
                  {/if}
                </p>
              {/if}
            </button>
          {/each}
        </div>
      </div>
    {/if}
  {/if}
</div>
