// Playlists: local + YouTube-linked, item add/remove/reorder, the built-in Watch later shortcut, // and the push-to-YouTube plan/apply flow. Only touches core's req. import { req } from "./core"; import type { Video } from "./feed"; // Not exported: consumers compare `playlist.kind`/`.source` against string literals, they never // name these types — keeping them local satisfies the no-unused-exports gate (knip). type PlaylistKind = "user" | "watch_later"; type PlaylistSource = "local" | "youtube"; export interface Playlist { id: number; name: string; kind: PlaylistKind; source: PlaylistSource; yt_playlist_id: string | null; dirty: boolean; // linked local playlist has edits not yet pushed to YouTube item_count: number; total_duration_seconds: number; cover_thumbnail: string | null; has_video?: boolean; // only set when listed with ?contains= } export interface PlaylistDetail { id: number; name: string; kind: PlaylistKind; source: PlaylistSource; yt_playlist_id: string | null; dirty: boolean; items: Video[]; } export interface PushPlan { action: "create" | "update"; to_insert: number; to_delete: number; to_reorder: number; yt_extra: number; // items on YouTube that the push would remove (divergence) units_estimate: number; remaining_today: number; affordable: boolean; } export interface PushResult { created: number; inserted: number; deleted: number; reordered: number; failures: string[]; yt_playlist_id: string | null; } export const playlistsApi = { playlists: (containsVideoId?: string): Promise => req(`/api/playlists${containsVideoId ? `?contains=${containsVideoId}` : ""}`), playlist: (id: number): Promise => req(`/api/playlists/${id}`), createPlaylist: (name: string): Promise => req("/api/playlists", { method: "POST", body: JSON.stringify({ name }) }), renamePlaylist: (id: number, name: string): Promise => req(`/api/playlists/${id}`, { method: "PATCH", body: JSON.stringify({ name }) }), deletePlaylist: (id: number, onYoutube = false) => req(`/api/playlists/${id}${onYoutube ? "?on_youtube=true" : ""}`, { method: "DELETE" }), addToPlaylist: (id: number, videoId: string) => req(`/api/playlists/${id}/items`, { method: "POST", body: JSON.stringify({ video_id: videoId }), }), removeFromPlaylist: (id: number, videoId: string) => req(`/api/playlists/${id}/items/${videoId}`, { method: "DELETE" }), reorderPlaylist: (id: number, videoIds: string[]) => req(`/api/playlists/${id}/order`, { method: "PUT", body: JSON.stringify({ video_ids: videoIds }), }), // Bookmark shortcut for the built-in Watch later playlist. watchLaterAdd: (videoId: string) => req("/api/playlists/watch-later", { method: "POST", body: JSON.stringify({ video_id: videoId }), }), watchLaterRemove: (videoId: string) => req(`/api/playlists/watch-later/${videoId}`, { method: "DELETE" }), syncYoutubePlaylists: (): Promise<{ synced: number; reason?: string }> => req("/api/playlists/sync-youtube", { method: "POST" }), revertPlaylist: (id: number): Promise<{ items: number }> => req(`/api/playlists/${id}/revert-youtube`, { method: "POST" }), playlistPushPlan: (id: number): Promise => req(`/api/playlists/${id}/push-plan`), pushPlaylist: (id: number): Promise => req(`/api/playlists/${id}/push`, { method: "POST" }), };