// Presentation logic for the pending-sync queue.
//
// The queue's *meaning* lives in Rust (which operations exist, how they push,
// when one is abandoned — DR-131). What lives here is purely how a row reads on
// screen: its label, its subtitle, and whether it is currently erroring. Kept
// out of the component so it can be unit-tested, the same pattern as
// episodeStrip.ts.
//
// TRACES: UR-025 | DR-132 | UT-123

import type { SyncQueueItem } from "$lib/api/bindings";

/** How each queued operation reads in the list. */
const OPERATION_LABELS: Record<string, string> = {
  report_playback_start: "Playback started",
  report_playback_stopped: "Watch position",
  update_progress: "Watch position",
  mark_played: "Marked as watched",
  mark_unplayed: "Marked as unwatched",
  mark_favorite: "Added to favourites",
  unmark_favorite: "Removed from favourites",
  playlist_create: "Playlist created",
  playlist_delete: "Playlist deleted",
  playlist_rename: "Playlist renamed",
  playlist_add_items: "Added to playlist",
  playlist_remove_items: "Removed from playlist",
  playlist_reorder_item: "Playlist reordered",
};

/**
 * A human label for a queued operation. An operation this build has no label
 * for still reads as something — an unknown row is the case most worth showing,
 * since it is the one that will end up abandoned.
 */
export function describeOperation(operation: string): string {
  return OPERATION_LABELS[operation] ?? operation.replace(/_/g, " ");
}

/** What the row is about: the item's title if the catalog knows it, else its id. */
export function describeSubject(item: SyncQueueItem): string {
  return item.itemName ?? item.itemId ?? "Unknown item";
}

/**
 * A row is "stuck" once it has failed at least once — that is what justifies
 * showing its error, and what a Retry button acts on.
 */
export function isStuck(item: SyncQueueItem): boolean {
  return item.status === "failed" || item.retryCount > 0;
}

export interface PendingSyncSummary {
  total: number;
  stuck: number;
  /** True when every queued row has already failed — retrying needs the server. */
  allStuck: boolean;
}

export function summarize(items: SyncQueueItem[]): PendingSyncSummary {
  const stuck = items.filter(isStuck).length;
  return {
    total: items.length,
    stuck,
    allStuck: items.length > 0 && stuck === items.length,
  };
}

/**
 * Oldest first — the order they will be replayed in, so the list reads as the
 * queue it is. Rows without a timestamp sort last rather than being dropped.
 */
export function sortForDisplay(items: SyncQueueItem[]): SyncQueueItem[] {
  return [...items].sort((a, b) => {
    if (!a.createdAt && !b.createdAt) return a.id - b.id;
    if (!a.createdAt) return 1;
    if (!b.createdAt) return -1;
    const diff = Date.parse(a.createdAt) - Date.parse(b.createdAt);
    return diff !== 0 ? diff : a.id - b.id;
  });
}
