import Foundation

/// Media handed to an output when a load is redirected.
///
/// Deliberately thin. `DecoderPort.handleLoad` takes an `AVURLAsset` and an
/// `AVContentKeySession` — local playback machinery a remote output cannot
/// honour and could not construct. An output gets the URI and the metadata a
/// receiver needs in order to display something.
struct OutputMedia {
    let uri: String
    var mimeType: String?
    var title: String?
    var artist: String?
    var album: String?
    var artworkUri: String?
    var startPositionMs: Double?
}

/// PlaybackOutputPort — hexagonal port for an output that owns playback instead
/// of the local decoder.
///
/// When one is installed on the engine, decoder commands are redirected here
/// rather than to `DecoderPort`, and this port reports position and state back
/// through the same `DecoderEventCallbacks` the decoder uses. The C coordinator
/// therefore keeps processing every command and stays authoritative: a
/// redirected play still transitions the machine to PLAYING, and everything
/// downstream — store, hooks, now playing — is correct without knowing where
/// audio is going.
///
/// This is why the port exists rather than a second `DecoderPort`: that protocol
/// is typed against the local stack (`AVPlayer`, `AVURLAsset`), which a receiver
/// cannot supply.
///
/// Threading: all methods are called on the main thread, matching `DecoderPort`.
protocol PlaybackOutputPort: AnyObject {
    /// Feedback to the engine, in the vocabulary the decoder already reports.
    var callbacks: DecoderEventCallbacks { get set }

    func handleLoad(media: OutputMedia)
    func handlePlay()
    func handlePause()
    func handleSeek(positionMs: Double)
    func handleStop()
    func handleSetRate(_ rate: Double)
    func handleSetVolume(_ volume: Double)
    func handleSetMuted(_ muted: Bool)
}

/// Adapts an output that arrived across the ObjC boundary to the Swift port.
///
/// Plugin pods conform to `AviationPlaybackOutput` because they cannot import
/// the Aviation Swift module. Everything inside the engine works against
/// `PlaybackOutputPort`, so the crossing is confined to this one type.
final class ObjCPlaybackOutputAdapter: PlaybackOutputPort {
    var callbacks = DecoderEventCallbacks()

    private let output: AviationPlaybackOutput

    init(_ output: AviationPlaybackOutput) {
        self.output = output
    }

    func handleLoad(media: OutputMedia) {
        var metadata: [String: String] = [:]
        metadata["mimeType"] = media.mimeType
        metadata["title"] = media.title
        metadata["artist"] = media.artist
        metadata["album"] = media.album
        metadata["artworkUri"] = media.artworkUri

        output.outputLoad(
            withUri: media.uri,
            metadata: metadata,
            startPositionMs: media.startPositionMs ?? 0
        )
    }

    func handlePlay() { output.outputPlay() }
    func handlePause() { output.outputPause() }
    func handleSeek(positionMs: Double) { output.outputSeek(to: positionMs) }
    func handleStop() { output.outputStop() }
    func handleSetRate(_ rate: Double) { output.outputSetRate(rate) }
    func handleSetVolume(_ volume: Double) { output.outputSetVolume(volume) }
    func handleSetMuted(_ muted: Bool) { output.outputSetMuted(muted) }
}
