import Cocoa
import CoreAudio
import FlutterMacOS
import os.log

// ---------------------------------------------------------------------------
// MacOSAudioLifecycle
//
// Native-side MethodChannel handler for macOS audio lifecycle events.
// Closes the iOS / macOS asymmetry that the iOS AppDelegate handles via
// AVAudioSession (no AVAudioSession equivalent on macOS). The macOS
// equivalents of the iOS lifecycle events are:
//
//   * Default audio device change  — Core Audio HAL default-input /
//     default-output device property listeners (mirrors iOS route
//     change). Posted as `handleDefaultDeviceChange` with payload
//     `{role: 'input' | 'output'}`.
//
//   * VPIO AudioUnit configuration change — observed via the VPIO
//     unit's `kAudioUnitProperty_StreamFormat` property listener.
//     Posted as `handleConfigurationChange` with no payload.
//
// Channel name: `chanora/macos_audio_lifecycle`. The Dart side wires the
// matching `chanora/ios_audio_lifecycle`-shaped event surface in
// `audio_lifecycle_service.dart` and currently logs the events; the
// engine-restart call (FRB `macos_default_device_changed`) is a
// follow-up.
//
// Trace: SysRS-051 (macOS audio-lifecycle asymmetry), SysRS-311
// (the deferred macOS audio-lifecycle platform-adapter allocation).
// ---------------------------------------------------------------------------

private let kLogTag = "chanora_flutter.macos_audio_lifecycle"

final class MacOSAudioLifecycle: NSObject, FlutterPlugin {
    private var channel: FlutterMethodChannel?
    private var listenerBlocks: [AudioObjectID: AudioObjectPropertyListenerBlock] = [:]

    static func register(with registrar: FlutterPluginRegistrar) {
        let channel = FlutterMethodChannel(
            name: "chanora/macos_audio_lifecycle",
            binaryMessenger: registrar.messenger
        )
        let instance = MacOSAudioLifecycle()
        instance.channel = channel
        registrar.addMethodCallDelegate(instance, channel: channel)
        instance.startObserving()
    }

    func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
        // macOS audio lifecycle is a one-way push: Swift → Dart.
        // The Dart side never invokes methods on this channel.
        result(FlutterMethodNotImplemented)
    }

    // MARK: - Core Audio HAL listeners

    private func startObserving() {
        registerDefaultDeviceListener(role: "output")
        registerDefaultDeviceListener(role: "input")
    }

    private func registerDefaultDeviceListener(role: String) {
        let selector: AudioObjectPropertySelector = (role == "input")
            ? kAudioHardwarePropertyDefaultInputDevice
            : kAudioHardwarePropertyDefaultOutputDevice
        var address = AudioObjectPropertyAddress(
            mSelector: selector,
            mScope: kAudioObjectPropertyScopeGlobal,
            mElement: kAudioObjectPropertyElementMain
        )
        let objectID: AudioObjectID = AudioObjectID(kAudioObjectSystemObject)
        let channel = self.channel
        let block: AudioObjectPropertyListenerBlock = { _, _ in
            os_log("default %{public}@ device changed", log: OSLog(subsystem: kLogTag, category: "lifecycle"), type: .info, role)
            channel?.invokeMethod("handleDefaultDeviceChange", arguments: ["role": role])
        }
        let status = AudioObjectAddPropertyListener(objectID, &address, block, nil)
        if status == noErr {
            listenerBlocks[objectID + UInt32(role.hashValue & 0xFFFF)] = block
            os_log("registered default %{public}@ device listener", log: OSLog(subsystem: kLogTag, category: "lifecycle"), type: .info, role)
        } else {
            os_log("failed to register default %{public}@ device listener (OSStatus %{public}d)",
                   log: OSLog(subsystem: kLogTag, category: "lifecycle"),
                   type: .error, role, Int(status))
        }
    }

    deinit {
        // Best-effort cleanup; AudioObjectRemovePropertyListener only
        // matters if the system still holds the block.
        for (id, block) in listenerBlocks {
            for selector in [
                kAudioHardwarePropertyDefaultInputDevice,
                kAudioHardwarePropertyDefaultOutputDevice,
            ] {
                var address = AudioObjectPropertyAddress(
                    mSelector: selector,
                    mScope: kAudioObjectPropertyScopeGlobal,
                    mElement: kAudioObjectPropertyElementMain
                )
                _ = AudioObjectRemovePropertyListener(id, &address, block, nil)
            }
        }
    }
}
