import Foundation

// MARK: - Protocol Version

/// Current version of the remote control wire protocol.
nonisolated let RemoteProtocolVersion: Int = 1

// MARK: - Supporting Types

/// Snapshot of the host's playback state, sent to remote clients.
nonisolated struct PlaybackStatePayload: Codable, Equatable, Sendable {
    var trackId: Int64?
    var isPlaying: Bool
    var currentTime: Double
    var duration: Double
    var volume: Float
    var isShuffled: Bool
}

/// Exchanged during connection setup to agree on protocol version.
nonisolated struct HandshakeMessage: Codable, Equatable, Sendable {
    var protocolVersion: Int
    var appVersion: String
}

// MARK: - RemoteCommand

/// Commands sent from a remote client to the host.
/// Wire format: `{"type":"<case>","payload":{...}}` (payload omitted for cases with no associated values).
nonisolated enum RemoteCommand: Equatable, Sendable {
    case play(trackId: Int64, queueIds: [Int64])
    case pause
    case resume
    case next
    case previous
    case seek(position: Double)
    case setVolume(level: Float)
    case toggleShuffle
    case refreshDB
}

extension RemoteCommand: Codable {
    private enum TypeKey: String, Codable {
        case play, pause, resume, next, previous, seek, setVolume, toggleShuffle, refreshDB
    }

    private enum CodingKeys: String, CodingKey {
        case type, payload
    }

    // Payload structs for cases with associated values
    private struct PlayPayload: Codable {
        var trackId: Int64
        var queueIds: [Int64]
    }

    private struct SeekPayload: Codable {
        var position: Double
    }

    private struct VolumePayload: Codable {
        var level: Float
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        switch self {
        case .play(let trackId, let queueIds):
            try container.encode(TypeKey.play, forKey: .type)
            try container.encode(PlayPayload(trackId: trackId, queueIds: queueIds), forKey: .payload)
        case .pause:
            try container.encode(TypeKey.pause, forKey: .type)
        case .resume:
            try container.encode(TypeKey.resume, forKey: .type)
        case .next:
            try container.encode(TypeKey.next, forKey: .type)
        case .previous:
            try container.encode(TypeKey.previous, forKey: .type)
        case .seek(let position):
            try container.encode(TypeKey.seek, forKey: .type)
            try container.encode(SeekPayload(position: position), forKey: .payload)
        case .setVolume(let level):
            try container.encode(TypeKey.setVolume, forKey: .type)
            try container.encode(VolumePayload(level: level), forKey: .payload)
        case .toggleShuffle:
            try container.encode(TypeKey.toggleShuffle, forKey: .type)
        case .refreshDB:
            try container.encode(TypeKey.refreshDB, forKey: .type)
        }
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let type = try container.decode(TypeKey.self, forKey: .type)
        switch type {
        case .play:
            let payload = try container.decode(PlayPayload.self, forKey: .payload)
            self = .play(trackId: payload.trackId, queueIds: payload.queueIds)
        case .pause:
            self = .pause
        case .resume:
            self = .resume
        case .next:
            self = .next
        case .previous:
            self = .previous
        case .seek:
            let payload = try container.decode(SeekPayload.self, forKey: .payload)
            self = .seek(position: payload.position)
        case .setVolume:
            let payload = try container.decode(VolumePayload.self, forKey: .payload)
            self = .setVolume(level: payload.level)
        case .toggleShuffle:
            self = .toggleShuffle
        case .refreshDB:
            self = .refreshDB
        }
    }
}

// MARK: - HostEvent

/// Events sent from the host to remote clients.
/// Wire format: `{"type":"<case>","payload":{...}}` (payload omitted for cases with no associated values).
nonisolated enum HostEvent: Equatable, Sendable {
    case playbackState(PlaybackStatePayload)
    case dbReady
    case error(message: String)
}

extension HostEvent: Codable {
    private enum TypeKey: String, Codable {
        case playbackState, dbReady, error
    }

    private enum CodingKeys: String, CodingKey {
        case type, payload
    }

    private struct ErrorPayload: Codable {
        var message: String
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        switch self {
        case .playbackState(let payload):
            try container.encode(TypeKey.playbackState, forKey: .type)
            try container.encode(payload, forKey: .payload)
        case .dbReady:
            try container.encode(TypeKey.dbReady, forKey: .type)
        case .error(let message):
            try container.encode(TypeKey.error, forKey: .type)
            try container.encode(ErrorPayload(message: message), forKey: .payload)
        }
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let type = try container.decode(TypeKey.self, forKey: .type)
        switch type {
        case .playbackState:
            let payload = try container.decode(PlaybackStatePayload.self, forKey: .payload)
            self = .playbackState(payload)
        case .dbReady:
            self = .dbReady
        case .error:
            let payload = try container.decode(ErrorPayload.self, forKey: .payload)
            self = .error(message: payload.message)
        }
    }
}
