// Continue Watching stale-entry suppression.
//
// Continue Watching is built from raw resume positions, so an episode the user
// has moved past keeps showing up as a resume suggestion — most visibly after
// skipping an episode, which leaves a partial position behind. Next Up already
// tells us where the user actually is in each series, so an in-progress episode
// that sits *behind* its series' Next Up entry is stale and gets suppressed.
//
// This is presentation-layer de-duplication over two lists the frontend already
// holds — no Jellyfin taxonomy involved, so it stays in `src/`.
//
// TRACES: UR-059 | DR-089
import type { MediaItem } from "$lib/api/types";

/**
 * Position of an episode within its series, as (season, episode).
 *
 * Returns null when the episode number is unknown — without it there is no
 * defensible ordering and we must not suppress anything. A missing *season*
 * number is normal for flat series (no season folders), so it is only usable
 * when both sides are equally season-less; callers compare via `isAheadOf`.
 */
function episodeOrder(item: MediaItem): { season: number | null; index: number } | null {
  if (item.indexNumber == null) return null;
  return { season: item.parentIndexNumber ?? null, index: item.indexNumber };
}

/** Is `a` strictly later in series order than `b`? */
function isAheadOf(a: MediaItem, b: MediaItem): boolean {
  const oa = episodeOrder(a);
  const ob = episodeOrder(b);
  if (!oa || !ob) return false;

  // Mixed season-numbering (one side foldered, the other flat) is not safely
  // comparable — leave the entry alone rather than hide something wrongly.
  if ((oa.season == null) !== (ob.season == null)) return false;

  if (oa.season != null && ob.season != null && oa.season !== ob.season) {
    return oa.season > ob.season;
  }
  return oa.index > ob.index;
}

/**
 * Drop resume entries the user has already moved past.
 *
 * An episode is suppressed when its series has a Next Up entry strictly later
 * in series order. Movies, items without a series, and anything whose ordering
 * is unknown are always kept — suppression must never hide something the user
 * genuinely still wants to resume.
 */
export function filterSupersededResumeItems(
  resumeItems: MediaItem[],
  nextUpItems: MediaItem[]
): MediaItem[] {
  if (nextUpItems.length === 0) return resumeItems;

  // Furthest-ahead Next Up entry per series: Next Up can carry more than one
  // entry for a series, and the latest is the true watch frontier.
  const frontier = new Map<string, MediaItem>();
  for (const item of nextUpItems) {
    if (!item.seriesId) continue;
    const current = frontier.get(item.seriesId);
    if (!current || isAheadOf(item, current)) {
      frontier.set(item.seriesId, item);
    }
  }

  return resumeItems.filter(item => {
    if (item.kind !== "episode" || !item.seriesId) return true;
    const ahead = frontier.get(item.seriesId);
    if (!ahead) return true;
    return !isAheadOf(ahead, item);
  });
}
