/**
 * Continue Watching stale-entry suppression tests.
 *
 * A partially-watched episode should drop off Continue Watching once the user
 * has moved past it — i.e. when Next Up for that series points at a *later*
 * episode. Otherwise skipping an episode leaves it lingering as a resume
 * suggestion behind the episode the user is actually on.
 *
 * The mirror case: an episode still under way must not appear in Next Up, which
 * is what made the two rows render the same cards.
 *
 * TRACES: UR-059 | DR-089, DR-197 | UT-192
 */

import { describe, it, expect } from "vitest";
import type { MediaItem } from "$lib/api/types";
import {
  filterSupersededResumeItems,
  filterInProgressNextUpItems,
} from "./continueWatchingFilter";

function episode(
  id: string,
  seriesId: string,
  season: number | undefined,
  index: number | undefined
): MediaItem {
  return {
    id,
    name: `Episode ${index}`,
    kind: "episode",
    seriesId,
    parentIndexNumber: season,
    indexNumber: index,
  } as MediaItem;
}

function movie(id: string): MediaItem {
  return { id, name: "A Movie", kind: "movie" } as MediaItem;
}

describe("filterSupersededResumeItems", () => {
  it("drops a partially-watched episode when next up is later in the same season", () => {
    const resume = [episode("s1e2", "series-a", 1, 2)];
    const nextUp = [episode("s1e5", "series-a", 1, 5)];

    const result = filterSupersededResumeItems(resume, nextUp);

    expect(result).toEqual([]);
  });

  it("drops it when next up is in a later season", () => {
    const resume = [episode("s1e9", "series-a", 1, 9)];
    const nextUp = [episode("s2e1", "series-a", 2, 1)];

    expect(filterSupersededResumeItems(resume, nextUp)).toEqual([]);
  });

  it("keeps the episode the user is actually mid-way through", () => {
    const resume = [episode("s1e4", "series-a", 1, 4)];
    const nextUp = [episode("s1e4", "series-a", 1, 4)];

    expect(filterSupersededResumeItems(resume, nextUp)).toHaveLength(1);
  });

  it("keeps an episode ahead of next up (user jumped forward)", () => {
    const resume = [episode("s1e7", "series-a", 1, 7)];
    const nextUp = [episode("s1e3", "series-a", 1, 3)];

    expect(filterSupersededResumeItems(resume, nextUp)).toHaveLength(1);
  });

  it("only compares within the same series", () => {
    const resume = [episode("a-s1e2", "series-a", 1, 2)];
    const nextUp = [episode("b-s1e9", "series-b", 1, 9)];

    expect(filterSupersededResumeItems(resume, nextUp)).toHaveLength(1);
  });

  it("never suppresses movies", () => {
    const resume = [movie("movie-1")];
    const nextUp = [episode("s1e5", "series-a", 1, 5)];

    expect(filterSupersededResumeItems(resume, nextUp)).toHaveLength(1);
  });

  it("keeps items when ordering is unknown on either side", () => {
    const resume = [episode("s1e2", "series-a", undefined, undefined)];
    const nextUp = [episode("s1e5", "series-a", 1, 5)];

    expect(filterSupersededResumeItems(resume, nextUp)).toHaveLength(1);
  });

  it("treats a missing season number as season 1 only when both sides agree", () => {
    // Flat series (no season folders): episode numbers alone must still order.
    const resume = [episode("e2", "series-a", undefined, 2)];
    const nextUp = [episode("e6", "series-a", undefined, 6)];

    expect(filterSupersededResumeItems(resume, nextUp)).toEqual([]);
  });

  it("is a no-op when next up is empty", () => {
    const resume = [episode("s1e2", "series-a", 1, 2)];

    expect(filterSupersededResumeItems(resume, [])).toHaveLength(1);
  });

  it("preserves the original order of surviving items", () => {
    const resume = [
      episode("a-s1e2", "series-a", 1, 2),
      episode("b-s1e1", "series-b", 1, 1),
      episode("c-s1e3", "series-c", 1, 3),
    ];
    const nextUp = [episode("b-s1e4", "series-b", 1, 4)];

    const result = filterSupersededResumeItems(resume, nextUp);

    expect(result.map(i => i.id)).toEqual(["a-s1e2", "c-s1e3"]);
  });

  it("uses the furthest-ahead next-up entry for a series", () => {
    const resume = [episode("s1e2", "series-a", 1, 2)];
    const nextUp = [
      episode("s1e1", "series-a", 1, 1),
      episode("s1e8", "series-a", 1, 8),
    ];

    expect(filterSupersededResumeItems(resume, nextUp)).toEqual([]);
  });
});

describe("filterInProgressNextUpItems", () => {
  it("drops the episode the viewer is mid-way through", () => {
    // The same episode in both lists is the duplicate-row bug: an in-progress
    // episode belongs to Continue Watching, never to Next Up.
    const resume = [episode("s1e4", "series-a", 1, 4)];
    const nextUp = [episode("s1e4", "series-a", 1, 4)];

    expect(filterInProgressNextUpItems(nextUp, resume)).toEqual([]);
  });

  it("keeps the genuinely unstarted next episode", () => {
    const resume = [episode("s1e4", "series-a", 1, 4)];
    const nextUp = [episode("s1e5", "series-a", 1, 5)];

    expect(filterInProgressNextUpItems(nextUp, resume).map(i => i.id)).toEqual(["s1e5"]);
  });

  it("only suppresses the started episode, not the rest of the row", () => {
    const resume = [episode("a-s1e4", "series-a", 1, 4)];
    const nextUp = [
      episode("a-s1e4", "series-a", 1, 4),
      episode("b-s1e1", "series-b", 1, 1),
      episode("c-s2e3", "series-c", 2, 3),
    ];

    const result = filterInProgressNextUpItems(nextUp, resume);

    expect(result.map(i => i.id)).toEqual(["b-s1e1", "c-s2e3"]);
  });

  it("is a no-op when nothing is in progress", () => {
    const nextUp = [episode("s1e1", "series-a", 1, 1)];

    expect(filterInProgressNextUpItems(nextUp, [])).toHaveLength(1);
  });

  it("ignores resume entries for other media", () => {
    const resume = [movie("movie-1")];
    const nextUp = [episode("s1e1", "series-a", 1, 1)];

    expect(filterInProgressNextUpItems(nextUp, resume)).toHaveLength(1);
  });
});
