import Foundation // MARK: - Chad Music API Response Models /// A category entry from GET /api/cat/:category (e.g., album, artist, genre). struct ChadCategory: Codable, Identifiable, Hashable { let item: String let count: Int? var id: String { item } var name: String { item } enum CodingKeys: String, CodingKey { case item, count } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) // item may be a string ("Rock") or a number (2012) depending on category type if let stringValue = try? container.decode(String.self, forKey: .item) { item = stringValue } else if let intValue = try? container.decode(Int.self, forKey: .item) { item = String(intValue) } else if let doubleValue = try? container.decode(Double.self, forKey: .item) { // 2012.0 → "2012", but preserve fractional values if doubleValue == doubleValue.rounded() { item = String(Int(doubleValue)) } else { item = String(doubleValue) } } else { throw DecodingError.typeMismatch( String.self, DecodingError.Context( codingPath: container.codingPath + [CodingKeys.item], debugDescription: "Expected String, Int, or Double for 'item'" ) ) } count = try container.decodeIfPresent(Int.self, forKey: .count) } init(item: String, count: Int?) { self.item = item self.count = count } } /// An album from the Chad Music API. struct ChadAlbum: Codable, Identifiable, Hashable { let id: String let album: String? 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? 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. 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? let url: String let bitRate: Int? let year: Int? let cover: String? 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" } 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) } } /// Library statistics from GET /api/stats. struct ChadStats: Codable { let tracks: Int? let albums: Int? let artists: Int? let duration: String? } /// 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" } } }