import Domain import Foundation struct SeededGenerator: RandomNumberGenerator { private var state: UInt64 init(seed: UInt64) { state = seed &+ 0x9E37_79B9_7F4A_7C15 } mutating func next() -> UInt64 { state &+= 0x9E37_79B9_7F4A_7C15 var mixed = state mixed = (mixed ^ (mixed >> 30)) &* 0xBF58_476D_1CE4_E5B9 mixed = (mixed ^ (mixed >> 27)) &* 0x94D0_49BB_1331_11EB return mixed ^ (mixed >> 31) } } struct FakeCatalog { struct AlbumSeed { let title: String let year: Int let tracks: Int } struct ArtistSeed { let name: String let genre: String let albums: [AlbumSeed] } static let artists: [ArtistSeed] = [ ArtistSeed(name: "Velvet Harbor", genre: "Indie Pop", albums: [ AlbumSeed(title: "Salt in the Signal", year: 2024, tracks: 11), AlbumSeed(title: "Low Tide Radio", year: 2021, tracks: 10), AlbumSeed(title: "Postcards From Nowhere", year: 2018, tracks: 12), ]), ArtistSeed(name: "Нина Орлова", genre: "Alternative", albums: [ AlbumSeed(title: "Тихий час", year: 2025, tracks: 9), AlbumSeed(title: "Север внутри", year: 2022, tracks: 11), ]), ArtistSeed(name: "Kaito Mori", genre: "Electronic", albums: [ AlbumSeed(title: "Neon Monsoon", year: 2023, tracks: 8), AlbumSeed(title: "東京 After Hours", year: 2020, tracks: 10), AlbumSeed(title: "Glass Circuits", year: 2017, tracks: 9), AlbumSeed(title: "Soft Machines (Remixes)", year: 2024, tracks: 6), ]), ArtistSeed(name: "The Paper Lanterns", genre: "Rock", albums: [ AlbumSeed(title: "Everything Burns Brighter at Night and Other Small Disasters", year: 2019, tracks: 13), AlbumSeed(title: "Static Bloom", year: 2016, tracks: 11), AlbumSeed(title: "Live at the Old Observatory", year: 2022, tracks: 16), ]), ArtistSeed(name: "Marisol Vega", genre: "Latin", albums: [ AlbumSeed(title: "Corazón de Verano", year: 2024, tracks: 12), AlbumSeed(title: "Luna Roja", year: 2021, tracks: 10), ]), ArtistSeed(name: "Oskar Lindqvist Trio", genre: "Jazz", albums: [ AlbumSeed(title: "Blue Hours", year: 2015, tracks: 7), AlbumSeed(title: "Winter Standards", year: 2018, tracks: 9), AlbumSeed(title: "Stockholm Sessions", year: 2023, tracks: 8), ]), ArtistSeed(name: "DJ Quartz", genre: "Hip-Hop", albums: [ AlbumSeed(title: "Concrete Gardens", year: 2022, tracks: 14), AlbumSeed(title: "No Signal", year: 2025, tracks: 12), ]), ArtistSeed(name: "Aurora Fields", genre: "Ambient", albums: [ AlbumSeed(title: "Weightless", year: 2020, tracks: 6), AlbumSeed(title: "Slow Light", year: 2023, tracks: 7), AlbumSeed(title: "Music for Empty Airports", year: 2017, tracks: 5), ]), ArtistSeed(name: "Holloway & Finch", genre: "Folk", albums: [ AlbumSeed(title: "The Orchard Tapes", year: 2019, tracks: 10), AlbumSeed(title: "Rivers We Crossed", year: 2024, tracks: 11), ]), ArtistSeed(name: "Сёстры Грома", genre: "Rock", albums: [ AlbumSeed(title: "Электричество", year: 2023, tracks: 10), AlbumSeed(title: "Город без сна", year: 2020, tracks: 12), ]), ArtistSeed(name: "Camille Durand", genre: "Classical", albums: [ AlbumSeed(title: "Nocturnes & Preludes", year: 2021, tracks: 14), AlbumSeed(title: "Satie: Works for Piano", year: 2016, tracks: 12), ]), ArtistSeed(name: "Midnight Arcade", genre: "Electronic", albums: [ AlbumSeed(title: "Insert Coin", year: 2018, tracks: 10), AlbumSeed(title: "High Score", year: 2021, tracks: 11), AlbumSeed(title: "Continue?", year: 2025, tracks: 9), ]), ArtistSeed(name: "Juno Okafor", genre: "R&B/Soul", albums: [ AlbumSeed(title: "Honey & Smoke", year: 2024, tracks: 12), AlbumSeed(title: "Afterglow", year: 2022, tracks: 10), ]), ArtistSeed(name: "Iron Meridian", genre: "Metal", albums: [ AlbumSeed(title: "Cathedral of Ash", year: 2019, tracks: 9), AlbumSeed(title: "Tidal Lock", year: 2023, tracks: 8), ]), ] static let openers = [ "Midnight", "Paper", "Golden", "Silent", "Electric", "Hollow", "Crimson", "Distant", "Velvet", "Broken", "Summer", "Neon", "Quiet", "Wild", "Frozen", "Burning", "Little", "Endless", "Lonely", "Silver", ] static let closers = [ "Hearts", "Rivers", "Machines", "Letters", "Highways", "Gardens", "Satellites", "Windows", "Echoes", "Tides", "Dreams", "Wires", "Shadows", "Mornings", "Skylines", "Ghosts", "Oceans", "Rooms", "Signals", "Fires", ] static let singles = [ "Interlude", "Overture", "Home", "Gravity", "Again", "Stay", "Run", "Waves", "Closer", "Tomorrow", "Если бы", "Навсегда", "Après la pluie", "Sin Ti", "さよなら", ] static let suffixes = ["", "", "", "", "", "", " (Reprise)", " (feat. Juno Okafor)", " (Acoustic)", ", Pt. 2"] static let formats: [AudioFormat] = [ AudioFormat(codec: .flac, bitrateKbps: 1011, sampleRate: 44100, bitDepth: 16), AudioFormat(codec: .flac, bitrateKbps: 2890, sampleRate: 96000, bitDepth: 24), AudioFormat(codec: .flac, bitrateKbps: 1411, sampleRate: 48000, bitDepth: 24), AudioFormat(codec: .mp3, bitrateKbps: 320, sampleRate: 44100), AudioFormat(codec: .aac, bitrateKbps: 256, sampleRate: 44100), ] let source: SourceID private(set) var changes: [Change] = [] init(source: SourceID, now: Date = Date()) { self.source = source var random = SeededGenerator(seed: 42) var allTracks: [Track] = [] var albumIndex = 0 for (artistIndex, seed) in Self.artists.enumerated() { let artistID = id("artist-\(artistIndex)") let artist = Artist( id: artistID, name: seed.name, sortName: seed.name.hasPrefix("The ") ? String(seed.name.dropFirst(4)) : seed.name, artwork: id("art-artist-\(artistIndex)"), albumCount: seed.albums.count ) changes.append(.upsert(.artist(artist))) for albumSeed in seed.albums { let tracks = makeTracks(albumSeed, albumIndex: albumIndex, artist: artist, genre: seed.genre, random: &random) let album = Album( id: id("album-\(albumIndex)"), title: albumSeed.title, artistID: artistID, artistName: seed.name, year: albumSeed.year, genre: seed.genre, artwork: id("art-album-\(albumIndex)"), trackCount: tracks.count, duration: tracks.reduce(0) { $0 + $1.duration }, addedAt: now.addingTimeInterval(-Double(Int.random(in: 1 ... 400, using: &random)) * 86400), bestFormat: tracks.first?.formats.first ) changes.append(.upsert(.album(album))) changes.append(contentsOf: tracks.map { .upsert(.track($0)) }) allTracks.append(contentsOf: tracks) albumIndex += 1 } } makePlaylists(from: allTracks, now: now, random: &random) makeAnnotations(for: allTracks, now: now, random: &random) makeStations() } private func id(_ remote: String) -> EntityID { EntityID(source: source, remote: remote) } private func makeTracks( _ seed: AlbumSeed, albumIndex: Int, artist: Artist, genre: String, random: inout SeededGenerator ) -> [Track] { let format = Self.formats[Int.random(in: 0 ..< Self.formats.count, using: &random)] return (1 ... seed.tracks).map { number in let base = Int.random(in: 0 ..< 5, using: &random) == 0 ? Self.singles.randomElement(using: &random) ?? "" : "\(Self.openers.randomElement(using: &random) ?? "") \(Self.closers.randomElement(using: &random) ?? "")" return Track( id: id("track-\(albumIndex)-\(number)"), title: base + (Self.suffixes.randomElement(using: &random) ?? ""), albumID: id("album-\(albumIndex)"), albumTitle: seed.title, artistID: artist.id, artistName: artist.name, trackNumber: number, duration: genre == "Ambient" || genre == "Jazz" ? Double(Int.random(in: 280 ... 620, using: &random)) : Double(Int.random(in: 118 ... 325, using: &random)), year: seed.year, genre: genre, artwork: id("art-album-\(albumIndex)"), isExplicit: genre == "Hip-Hop" && Int.random(in: 0 ..< 2, using: &random) == 0, formats: [format] ) } } private mutating func makePlaylists(from tracks: [Track], now: Date, random: inout SeededGenerator) { let seeds: [(name: String, summary: String)] = [ ("Late Night Drive", "Windows down, city lights."), ("Focus", "Instrumental and quiet."), ("Утро", ""), ("Gym", "Loud."), ("Sunday Kitchen", "Cooking with the radio on."), ] for (index, seed) in seeds.enumerated() { let picked = Array(tracks.shuffled(using: &random).prefix(12 + index * 5)) let playlist = Playlist( id: id("playlist-\(index)"), name: seed.name, summary: seed.summary, artwork: id("art-playlist-\(index)"), trackCount: picked.count, duration: picked.reduce(0) { $0 + $1.duration }, updatedAt: now.addingTimeInterval(-Double(index) * 86400 * 9) ) let entries = picked.enumerated().map { PlaylistEntry(id: "entry-\(index)-\($0.offset)", track: $0.element.id) } changes.append(.upsert(.playlist(playlist, entries: entries))) } } private mutating func makeAnnotations(for tracks: [Track], now: Date, random: inout SeededGenerator) { for track in tracks.shuffled(using: &random).prefix(140) { let plays = Int.random(in: 1 ... 60, using: &random) changes.append(.annotate(track.id, Annotation( favoritedAt: plays > 42 ? now.addingTimeInterval(-Double(plays) * 3600) : nil, playCount: plays, lastPlayedAt: now.addingTimeInterval(-Double(Int.random(in: 1 ... 900, using: &random)) * 3600) ))) } } private mutating func makeStations() { let seeds = [ ("Radio Paradise", "Eclectic, listener supported", "https://stream.radioparadise.com/flacm"), ("SomaFM Groove Salad", "Ambient and downtempo beats", "https://ice1.somafm.com/groovesalad-256-mp3"), ("KEXP 90.3", "Where the music matters", "https://kexp-mp3-128.streamguys1.com/kexp128.mp3"), ("FIP", "Radio France, sans frontières", "https://icecast.radiofrance.fr/fip-hifi.aac"), ] for (index, seed) in seeds.enumerated() { guard let url = URL(string: seed.2) else { continue } changes.append(.upsert(.station(RadioStation( id: id("station-\(index)"), name: seed.0, tagline: seed.1, streamURL: url, artwork: id("art-station-\(index)") )))) } } }