import Foundation

enum SourceMode: String {
    case radio = "RADIO"
    case library = "LIBRARY"
}

enum AuthState: String {
    case checking = "CHECKING"
    case signedOut = "SIGNED OUT"
    case signedIn = "SIGNED IN"
}

enum APIError: LocalizedError {
    case invalidURL
    case file(String)
    case http(Int, String)

    var errorDescription: String? {
        switch self {
        case .invalidURL:
            return "Invalid URL"
        case .file(let message):
            return message
        case .http(let status, let body):
            return "HTTP \(status): \(body)"
        }
    }
}

struct Track: Codable, Hashable, Identifiable {
    var id: String
    var filename: String
    var title: String
    var duration: Double
    var artist: String?
    var album: String?
    var available: Bool?

    init(
        id: String,
        filename: String,
        title: String,
        duration: Double,
        artist: String? = nil,
        album: String? = nil,
        available: Bool? = nil
    ) {
        self.id = id
        self.filename = filename
        self.title = title
        self.duration = duration
        self.artist = artist
        self.album = album
        self.available = available
    }
}

struct ChannelInfo: Decodable, Identifiable {
    let id: String
    let name: String
    let description: String
    let listenerCount: Int
    let isDefault: Bool
    let trackCount: Int
    let listeners: [String]

    enum CodingKeys: String, CodingKey {
        case id
        case name
        case description
        case listenerCount
        case isDefault
        case trackCount
        case listeners
    }

    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        id = try c.decode(String.self, forKey: .id)
        name = try c.decodeIfPresent(String.self, forKey: .name) ?? "Room"
        description = try c.decodeIfPresent(String.self, forKey: .description) ?? ""
        listenerCount = try c.decodeIfPresent(Int.self, forKey: .listenerCount) ?? 0
        isDefault = try c.decodeIfPresent(Bool.self, forKey: .isDefault) ?? false
        trackCount = try c.decodeIfPresent(Int.self, forKey: .trackCount) ?? 0
        listeners = try c.decodeIfPresent([String].self, forKey: .listeners) ?? []
    }
}

struct ChannelState: Decodable {
    let track: Track?
    let currentTimestamp: Double
    let channelName: String
    let channelId: String
    let paused: Bool
    let queue: [Track]?
    let currentIndex: Int
    let playbackMode: String
    let listenerCount: Int
    let listeners: [String]

    enum CodingKeys: String, CodingKey {
        case track
        case currentTimestamp
        case channelName
        case channelId
        case paused
        case queue
        case currentIndex
        case playbackMode
        case listenerCount
        case listeners
    }

    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        track = try c.decodeIfPresent(Track.self, forKey: .track)
        currentTimestamp = try c.decodeIfPresent(Double.self, forKey: .currentTimestamp) ?? 0
        channelName = try c.decodeIfPresent(String.self, forKey: .channelName) ?? ""
        channelId = try c.decodeIfPresent(String.self, forKey: .channelId) ?? ""
        paused = try c.decodeIfPresent(Bool.self, forKey: .paused) ?? true
        queue = try c.decodeIfPresent([Track].self, forKey: .queue)
        currentIndex = try c.decodeIfPresent(Int.self, forKey: .currentIndex) ?? 0
        playbackMode = try c.decodeIfPresent(String.self, forKey: .playbackMode) ?? "repeat-all"
        listenerCount = try c.decodeIfPresent(Int.self, forKey: .listenerCount) ?? 0
        listeners = try c.decodeIfPresent([String].self, forKey: .listeners) ?? []
    }
}

struct PlaylistBundle: Decodable {
    let mine: [Playlist]
    let shared: [Playlist]
}

struct Playlist: Decodable, Identifiable {
    let id: String
    let name: String
    let description: String
    let ownerId: Int
    let ownerName: String
    let isPublic: Bool
    let shareToken: String?
    let trackIds: [String]

    enum CodingKeys: String, CodingKey {
        case id
        case name
        case description
        case ownerId
        case ownerName
        case isPublic
        case shareToken
        case trackIds
    }

    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        id = try c.decode(String.self, forKey: .id)
        name = try c.decodeIfPresent(String.self, forKey: .name) ?? "Playlist"
        description = try c.decodeIfPresent(String.self, forKey: .description) ?? ""
        ownerId = try c.decodeIfPresent(Int.self, forKey: .ownerId) ?? 0
        ownerName = try c.decodeIfPresent(String.self, forKey: .ownerName) ?? ""
        isPublic = try c.decodeIfPresent(Bool.self, forKey: .isPublic) ?? false
        shareToken = try c.decodeIfPresent(String.self, forKey: .shareToken)
        trackIds = try c.decodeIfPresent([String].self, forKey: .trackIds) ?? []
    }
}

struct UserSession: Decodable {
    let id: Int
    let username: String
    let isAdmin: Bool
    let isGuest: Bool
    let permissions: [Permission]

    enum CodingKeys: String, CodingKey {
        case id
        case username
        case isAdmin
        case isAdminSnake = "is_admin"
        case isGuest
        case isGuestSnake = "is_guest"
        case permissions
    }

    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        id = try c.decodeIfPresent(Int.self, forKey: .id) ?? 0
        username = try c.decodeIfPresent(String.self, forKey: .username) ?? "guest"
        isAdmin = try c.decodeIfPresent(Bool.self, forKey: .isAdmin)
            ?? c.decodeIfPresent(Bool.self, forKey: .isAdminSnake)
            ?? false
        isGuest = try c.decodeIfPresent(Bool.self, forKey: .isGuest)
            ?? c.decodeIfPresent(Bool.self, forKey: .isGuestSnake)
            ?? false
        permissions = try c.decodeIfPresent([Permission].self, forKey: .permissions) ?? []
    }
}

struct Permission: Decodable {
    let resourceType: String
    let resourceId: String?
    let permission: String

    enum CodingKeys: String, CodingKey {
        case resourceType
        case resourceTypeSnake = "resource_type"
        case resourceId
        case resourceIdSnake = "resource_id"
        case permission
    }

    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        resourceType = try c.decodeIfPresent(String.self, forKey: .resourceType)
            ?? c.decodeIfPresent(String.self, forKey: .resourceTypeSnake)
            ?? ""
        resourceId = try c.decodeIfPresent(String.self, forKey: .resourceId)
            ?? c.decodeIfPresent(String.self, forKey: .resourceIdSnake)
        permission = try c.decodeIfPresent(String.self, forKey: .permission) ?? ""
    }
}

struct AuthEnvelope: Decodable {
    let user: UserSession?
    let permissions: [Permission]

    enum CodingKeys: String, CodingKey {
        case user
        case permissions
    }

    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        user = try c.decodeIfPresent(UserSession.self, forKey: .user)
        permissions = try c.decodeIfPresent([Permission].self, forKey: .permissions) ?? []
    }
}

struct QueueResponse: Decodable {
    let success: Bool?
    let queueLength: Int?
}

struct ModeResponse: Decodable {
    let success: Bool?
    let playbackMode: String?
}

struct FetchItem: Codable, Hashable {
    let id: String?
    let url: String
    let title: String
}

struct FetchPlaylistResponse: Decodable {
    let type: String
    let title: String
    let count: Int
    let items: [FetchItem]
    let requiresConfirmation: Bool?
}

struct FetchSingleResponse: Decodable {
    let type: String
    let id: String?
    let title: String
    let queueType: String?
}

enum FetchResponse: Decodable {
    case single(FetchSingleResponse)
    case playlist(FetchPlaylistResponse)

    enum CodingKeys: String, CodingKey {
        case type
    }

    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        let type = try c.decodeIfPresent(String.self, forKey: .type)
        switch type {
        case "playlist":
            self = .playlist(try FetchPlaylistResponse(from: decoder))
        default:
            self = .single(try FetchSingleResponse(from: decoder))
        }
    }
}

struct FetchConfirmResponse: Decodable {
    let message: String
    let queueType: String?
    let estimatedTime: String?
    let playlistId: String?
    let playlistName: String?
    let items: [FetchItem]?
}
