// TV library landing page data store.
// Powers the focused TV landing: hero + horizontal sliders.
// TRACES: UR-007, UR-023, UR-034 | DR-007, DR-038, DR-039
import { writable, derived } from "svelte/store";
import type { MediaItem } from "$lib/api/types";
import { auth } from "./auth";
import { buildHeroMix } from "$lib/utils/heroMix";

/** A single "by genre" row: the genre name plus the series in it. */
export interface GenreRow {
  id: string;
  name: string;
  items: MediaItem[];
}

interface TvState {
  // Episodes the user can resume (continue watching).
  continueWatching: MediaItem[];
  // Next unwatched episode per in-progress series.
  nextUp: MediaItem[];
  // Recently added items in the TV library (series/seasons/episodes).
  recentlyAdded: MediaItem[];
  // One slider per genre (top genres by show count).
  genreRows: GenreRow[];
  // Mix used for the hero banner.
  heroItems: MediaItem[];
  isLoading: boolean;
  error: string | null;
}

const SECTION_LIMIT = 16;
// How many genre sliders to show, and how many genres to probe to find them.
const MAX_GENRE_ROWS = 8;
const MAX_GENRES_PROBED = 20;

function createTvStore() {
  const initialState: TvState = {
    continueWatching: [],
    nextUp: [],
    recentlyAdded: [],
    genreRows: [],
    heroItems: [],
    isLoading: false,
    error: null,
  };

  const { subscribe, set, update } = writable<TvState>(initialState);

  /** Artwork check for hero candidates: own/parent backdrop or primary image. */
  const hasArt = (i: MediaItem) =>
    !!(i.backdropImageTags && i.backdropImageTags.length > 0) ||
    !!(i.parentBackdropImageTags && i.parentBackdropImageTags.length > 0) ||
    !!i.primaryImageTag;

  async function loadSections(libraryId: string) {
    update(s => ({
      ...s,
      isLoading: s.continueWatching.length === 0 && s.recentlyAdded.length === 0,
      error: null,
    }));

    try {
      const repo = auth.getRepository();

      const [resume, nextUp, latest, surprise] = await Promise.all([
        repo.getResumeItems(libraryId, SECTION_LIMIT),
        repo.getNextUpEpisodes(undefined, SECTION_LIMIT),
        repo.getLatestItems(libraryId, SECTION_LIMIT),
        // Random pool so the hero rotation changes between visits (SortBy=Random
        // shuffles server-side online, and via SQLite RANDOM() offline).
        repo
          .getItems(libraryId, {
            includeItemTypes: ["Series"],
            sortBy: "Random",
            recursive: true,
            limit: SECTION_LIMIT,
          })
          .then(r => r.items)
          .catch(() => [] as MediaItem[]),
      ]);

      // Resume items are already video-only from the server, but keep episodes
      // (and the occasional movie that lives in a mixed library) defensively.
      const continueWatching = resume.filter(i => i.type === "Episode" || i.type === "Movie");

      // Mix the hero: in-progress episodes first (most personal), then next-up,
      // recent additions, and random series from across the library.
      const heroItems = buildHeroMix([continueWatching, nextUp, latest, surprise], hasArt);

      update(s => ({
        ...s,
        continueWatching,
        nextUp,
        recentlyAdded: latest,
        heroItems,
        isLoading: false,
      }));

      // Genre rows are secondary — load them after the main sections paint so
      // the page isn't blocked on N per-genre queries.
      loadGenreRows(libraryId);
    } catch (error) {
      const message = error instanceof Error ? error.message : "Failed to load TV sections";
      update(s => ({ ...s, isLoading: false, error: message }));
      console.error("Failed to load TV sections:", error);
    }
  }

  /**
   * Build one slider per genre, showing the top-rated series in each. We probe
   * a bounded set of genres in parallel, drop empty ones, then keep the genres
   * with the most shows (so niche/near-empty genres don't crowd the page).
   */
  async function loadGenreRows(libraryId: string) {
    try {
      const repo = auth.getRepository();
      const genres = await repo.getGenres(libraryId);
      if (genres.length === 0) return;

      const probed = genres.slice(0, MAX_GENRES_PROBED);
      const rows = await Promise.all(
        probed.map(async (genre): Promise<GenreRow> => {
          try {
            const result = await repo.getItems(libraryId, {
              includeItemTypes: ["Series"],
              genres: [genre.name],
              sortBy: "CommunityRating",
              sortOrder: "Descending",
              recursive: true,
              limit: SECTION_LIMIT,
            });
            return { id: genre.id, name: genre.name, items: result.items };
          } catch (e) {
            console.warn(`Failed to load genre row "${genre.name}":`, e);
            return { id: genre.id, name: genre.name, items: [] };
          }
        })
      );

      const genreRows = rows
        .filter(row => row.items.length > 0)
        .sort((a, b) => b.items.length - a.items.length)
        .slice(0, MAX_GENRE_ROWS);

      update(s => ({ ...s, genreRows }));
    } catch (e) {
      console.warn("Failed to load TV genre rows:", e);
    }
  }

  function reset() {
    set(initialState);
  }

  return {
    subscribe,
    loadSections,
    reset,
  };
}

export const tv = createTvStore();

export const tvHeroItems = derived(tv, $t => $t.heroItems);
export const isTvLoading = derived(tv, $t => $t.isLoading);
