import CoreTransferable import Foundation import UniformTypeIdentifiers // MARK: - Custom UTTypes for drag-and-drop extension UTType { static let chadTrack = UTType(exportedAs: "com.mixboard.chad-track") static let chadAlbum = UTType(exportedAs: "com.mixboard.chad-album") } // MARK: - Chad Music API Response Models /// Navigation value for drilling into a category (e.g., artist "Pink Floyd" → show their albums). struct CategoryFilter: Hashable { let category: ChadCategoryType let value: String } /// A category entry from GET /api/cat/:category (e.g., album, artist, genre). /// Server returns: {"item": "Rock", "count": 42} struct ChadCategory: Codable, Identifiable, Hashable { let item: String let count: Int? // No server-side ID — use item name as identity var id: String { item } var name: String { item } } /// An album from the Chad Music API. /// Server keys: id, artist, year, album (title), publisher, country, genre, type, status, mb_id, track_count, total_duration, cover struct ChadAlbum: Codable, Identifiable, Hashable { let id: String let album: String? // server can return null for some releases let artist: String? let year: Int? let genre: String? let trackCount: Int? let cover: String? let publisher: String? let country: String? let type: String? let status: String? let totalDuration: Double? let originalDate: String? let mbId: String? /// Display-friendly title. var title: String { album ?? "Untitled" } enum CodingKeys: String, CodingKey { case id, album, artist, year, genre, cover, publisher, country case trackCount = "track_count" case totalDuration = "total_duration" case originalDate = "original_date" case mbId = "mb_id" case type, status } } /// A track from GET /api/album/:id/tracks. /// Server keys: id, artist, album_artist, album, year, no (track#), title, bit_rate, duration, url, cover struct ChadTrack: Codable, Identifiable, Hashable { let id: String let title: String let artist: String? let albumArtist: String? let album: String? let duration: Double? let no: Int? // server calls track number "no" let url: String // relative path for streaming let bitRate: Int? let year: Int? let cover: String? /// Track number for display. var trackNumber: Int? { no } enum CodingKeys: String, CodingKey { case id, title, artist, album, duration, no, url, year, cover case albumArtist = "album_artist" case bitRate = "bit_rate" } /// Human-readable duration string. var formattedDuration: String { guard let duration else { return "—" } let total = Int(duration) let minutes = total / 60 let seconds = total % 60 return String(format: "%d:%02d", minutes, seconds) } } extension ChadTrack: Transferable { static var transferRepresentation: some TransferRepresentation { CodableRepresentation(contentType: .chadTrack) } } extension ChadAlbum: Transferable { static var transferRepresentation: some TransferRepresentation { CodableRepresentation(contentType: .chadAlbum) } } /// The album-tracks endpoint returns a plain array of tracks (not a wrapper object). /// We decode as [ChadTrack] directly in the API client. /// Library statistics from GET /api/stats. /// Server keys: tracks, albums, artists, duration (formatted string), rescans struct ChadStats: Codable { let tracks: Int? let albums: Int? let artists: Int? let duration: String? // server returns pre-formatted string like "3d 14h 22m" } /// The browsable category types exposed by the API. enum ChadCategoryType: String, CaseIterable, Identifiable { case album case artist case genre case year case publisher case country case type case status var id: String { rawValue } var displayName: String { switch self { case .album: "Albums" case .artist: "Artists" case .genre: "Genres" case .year: "Years" case .publisher: "Publishers" case .country: "Countries" case .type: "Types" case .status: "Status" } } var icon: String { switch self { case .album: "square.stack" case .artist: "person.2" case .genre: "guitars" case .year: "calendar" case .publisher: "building.2" case .country: "globe" case .type: "tag" case .status: "checkmark.circle" } } }