import { createSpotifyApiClient, type SpotifyApiClient } from "./api-client.js"; import type { CliDeps, ParsedCli } from "./cli.js"; import { mapPlaylist, mapTrack } from "./search.js"; import type { SpotifyTrack } from "./types.js"; type PlaylistClient = Pick; export function validateTrackUris(uris: string[]): string[] { if (uris.length === 0) { throw new Error("At least one spotify:track URI is required."); } for (const uri of uris) { if (!uri.startsWith("spotify:track:")) { throw new Error(`Invalid Spotify track URI: ${uri}`); } } return uris; } function snapshotIds(results: Array<{ snapshot_id?: string }>): string[] { return results.map((result) => result.snapshot_id).filter((id): id is string => Boolean(id)); } export async function runCreatePlaylistCommand( args: ParsedCli, deps: CliDeps, client: PlaylistClient = createSpotifyApiClient() ): Promise { const name = args.positional.join(" ").trim(); if (!name) { throw new Error("Missing playlist name."); } const playlist = mapPlaylist(await client.createPlaylist(name, { description: args.description, public: Boolean(args.public) })); if (args.json) { deps.stdout.write(`${JSON.stringify({ playlist }, null, 2)}\n`); } else { deps.stdout.write(`Created playlist ${playlist.id}: ${playlist.name}\n`); } return 0; } export async function runAddToPlaylistCommand( args: ParsedCli, deps: CliDeps, client: PlaylistClient = createSpotifyApiClient() ): Promise { const [playlistId, ...uris] = args.positional; if (!playlistId) { throw new Error("Missing playlist id."); } const validUris = validateTrackUris(uris); const results = await client.addItemsToPlaylist(playlistId, validUris); const output = { playlistId, count: validUris.length, snapshotIds: snapshotIds(results) }; deps.stdout.write(args.json ? `${JSON.stringify(output, null, 2)}\n` : `Added ${output.count} track(s) to ${playlistId}.\n`); return 0; } export async function runRemoveFromPlaylistCommand( args: ParsedCli, deps: CliDeps, client: PlaylistClient = createSpotifyApiClient() ): Promise { const [playlistId, ...uris] = args.positional; if (!playlistId) { throw new Error("Missing playlist id."); } const validUris = validateTrackUris(uris); const results = await client.removeItemsFromPlaylist(playlistId, validUris); const output = { playlistId, count: validUris.length, snapshotIds: snapshotIds(results) }; deps.stdout.write(args.json ? `${JSON.stringify(output, null, 2)}\n` : `Removed ${output.count} track(s) from ${playlistId}.\n`); return 0; } export async function searchAndAdd( playlistId: string, queries: string[], client: Pick ): Promise<{ added: Array<{ query: string; uri: string; name: string; artists: string[] }>; missed: string[]; snapshotIds: string[] }> { if (!playlistId) { throw new Error("Missing playlist id."); } if (queries.length === 0) { throw new Error("At least one search query is required."); } const added: Array<{ query: string; uri: string; name: string; artists: string[] }> = []; const missed: string[] = []; for (const query of queries) { const tracks: SpotifyTrack[] = await client.searchTracks(query, 1); const first = tracks[0]; if (!first) { missed.push(query); continue; } const output = mapTrack(first); added.push({ query, uri: output.uri, name: output.name, artists: output.artists }); } const mutationResults = added.length > 0 ? await client.addItemsToPlaylist(playlistId, added.map((entry) => entry.uri)) : []; return { added, missed, snapshotIds: snapshotIds(mutationResults) }; } export async function runSearchAndAddCommand( args: ParsedCli, deps: CliDeps, client: PlaylistClient = createSpotifyApiClient() ): Promise { const [playlistId, ...queries] = args.positional; const output = await searchAndAdd(playlistId, queries, client); if (args.json) { deps.stdout.write(`${JSON.stringify(output, null, 2)}\n`); } else { deps.stdout.write(`Added ${output.added.length} track(s) to ${playlistId}; missed ${output.missed.length}.\n`); } return 0; }