// The Episode Focus View is the *only* episode surface (ux-flows §5B.1), so it
// has to carry everything the bare Episode page used to: download, breadcrumbs
// back to the series/season, cast and similar shows. It shipped with only Play
// and Favourite, which is why "open an episode from Continue Watching" lost the
// download affordance.
//
// TRACES: UR-048, UR-058 | DR-062, DR-142 | UT-131, UT-132, UT-133, UT-134, UT-135
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen } from "@testing-library/svelte";
import type { MediaItem } from "$lib/api/types";

const h = vi.hoisted(() => {
  function shim<T>(initial: T) {
    let value = initial;
    const subs = new Set<(v: T) => void>();
    return {
      set(v: T) {
        value = v;
        subs.forEach((fn) => fn(value));
      },
      subscribe(fn: (v: T) => void) {
        subs.add(fn);
        fn(value);
        return () => subs.delete(fn);
      },
    };
  }
  return {
    downloadsStore: shim({ downloads: {} as Record<string, unknown> }),
    favoriteOverridesStore: shim(new Map<string, boolean>()),
    getSimilarItems: vi.fn(async () => ({ items: [] as MediaItem[] })),
    search: vi.fn(async () => ({ items: [] as MediaItem[] })),
  };
});

vi.mock("$lib/stores/downloads", () => ({
  downloads: {
    subscribe: h.downloadsStore.subscribe,
    downloadVideo: vi.fn(),
    pinItem: vi.fn(),
    unpinItem: vi.fn(),
    delete: vi.fn(),
    cancel: vi.fn(),
  },
}));

vi.mock("$lib/stores/favorites", async () => {
  const actual = await vi.importActual<typeof import("$lib/stores/favorites")>(
    "$lib/stores/favorites"
  );
  return { ...actual, favoriteOverrides: { subscribe: h.favoriteOverridesStore.subscribe } };
});

vi.mock("$lib/stores/auth", () => ({
  auth: {
    getRepository: () => ({ getSimilarItems: h.getSimilarItems, search: h.search }),
    getUserId: () => "user-1",
  },
  user: { subscribe: (fn: (v: unknown) => void) => (fn({ id: "user-1" }), () => {}) },
}));

// CachedImage does async repo/image work irrelevant to these tests.
vi.mock("$lib/components/common/CachedImage.svelte", async () => ({
  default: (await import("./__mocks__/StubImage.svelte")).default,
}));

import EpisodeFocusView from "./EpisodeFocusView.svelte";

const SERIES: MediaItem = {
  id: "series-1",
  name: "The Show",
  kind: "series",
  genres: ["Drama"],
  people: [{ id: "p-1", name: "Lead Actor", type: "Actor" }],
} as unknown as MediaItem;

function episode(overrides: Partial<MediaItem> = {}): MediaItem {
  return {
    id: "ep-4",
    name: "The Fourth One",
    kind: "episode",
    seriesId: "series-1",
    seriesName: "The Show",
    parentIndexNumber: 2,
    indexNumber: 4,
    durationMs: 2_880_000,
    overview: "Something happens.",
    genres: ["Drama"],
    people: [{ id: "p-1", name: "Lead Actor", type: "Actor" }],
    ...overrides,
  } as unknown as MediaItem;
}

function sibling(id: string, number: number): MediaItem {
  return {
    id,
    name: `Episode ${number}`,
    kind: "episode",
    seriesId: "series-1",
    parentIndexNumber: 2,
    indexNumber: number,
  } as unknown as MediaItem;
}

const allEpisodes = [sibling("ep-3", 3), episode(), sibling("ep-5", 5)];

describe("EpisodeFocusView — full episode functionality (DR-142)", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    h.downloadsStore.set({ downloads: {} });
    h.favoriteOverridesStore.set(new Map());
  });

  it("offers a download control in the hero", () => {
    render(EpisodeFocusView, {
      props: { episode: episode(), series: SERIES, allEpisodes },
    });

    expect(screen.getByLabelText(/Download for offline playback/i)).toBeTruthy();
  });

  it("links the series name back to the series page", () => {
    render(EpisodeFocusView, {
      props: { episode: episode(), series: SERIES, allEpisodes },
    });

    const link = screen.getByRole("link", { name: "The Show" });
    expect(link.getAttribute("href")).toBe("/library/series-1");
  });

  it("links the season badge to the season's place in the series list", () => {
    render(EpisodeFocusView, {
      props: { episode: episode(), series: SERIES, allEpisodes },
    });

    const link = screen.getByRole("link", { name: "S2E4" });
    expect(link.getAttribute("href")).toBe("/library/series-1#season-2");
  });

  it("renders cast below the episode strip, never above it (DR-062)", () => {
    const { container } = render(EpisodeFocusView, {
      props: { episode: episode(), series: SERIES, allEpisodes },
    });

    const headings = [...container.querySelectorAll("h2")].map((h2) => h2.textContent?.trim());
    const strip = headings.indexOf("More Episodes");
    const cast = headings.findIndex((t) => t?.startsWith("Cast"));

    expect(strip).toBeGreaterThanOrEqual(0);
    expect(cast).toBeGreaterThan(strip);
  });

  it("hides the episode strip when the episode has no siblings", () => {
    render(EpisodeFocusView, {
      props: { episode: episode(), series: SERIES, allEpisodes: [] },
    });

    expect(screen.queryByText("More Episodes")).toBeNull();
  });

  it("renders without a series for an episode that carries no seriesId", () => {
    render(EpisodeFocusView, {
      props: {
        episode: episode({ seriesId: null, seriesName: null }),
        series: null,
        allEpisodes: [],
      },
    });

    // Still a complete surface: title, play and download all present.
    expect(screen.getByText("The Fourth One")).toBeTruthy();
    expect(screen.getByLabelText(/Download for offline playback/i)).toBeTruthy();
    expect(screen.queryByRole("link", { name: "The Show" })).toBeNull();
  });
});
