import Foundation
import Capacitor
import AVFoundation

// Call-audio routing for the iOS app. Registered by cap sync as a real Capacitor plugin package, so it shows
// up as window.Capacitor.Plugins.AudioRoute (an app-embedded class gets stripped in release builds).
//
// KNOWN CEILING (WKWebView): the app does NOT own the AVAudioSession — WebKit's media process does, and it
// re-asserts its own category (Bluetooth allowed) on every change. So the earpiece can't be forced, and the
// built-in speaker can't be held over an actively-connected Bluetooth device (a re-force just oscillates with
// WebKit — proven on device). This plugin therefore does a SINGLE override per user tap and does NOT fight
// route changes; it only reports the active output so the web UI can show the correct icon.
//   * setSpeaker(true)  -> try to force the loudspeaker (drops Bluetooth options + overrideOutputAudioPort(.speaker))
//   * setSpeaker(false) -> "Device": allow BT/wired and use the default port (routes to a connected headset)
//   * getRoute() + 'routeChange' event -> the active output ('speaker'|'bluetooth'|'wired'|'receiver'|'airplay')
@objc(AudioRoutePlugin)
public class AudioRoutePlugin: CAPPlugin, CAPBridgedPlugin {
    public let identifier = "AudioRoutePlugin"
    public let jsName = "AudioRoute"
    public let pluginMethods: [CAPPluginMethod] = [
        CAPPluginMethod(name: "setSpeaker", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "getRoute", returnType: CAPPluginReturnPromise)
    ]

    private static let nativeTag = "1.1.2-stable"
    private var pending: DispatchWorkItem?

    override public func load() {
        try? configureDevice()   // start in "device" mode (BT/wired allowed); the web forces speaker per call
        NotificationCenter.default.addObserver(self, selector: #selector(routeChanged),
                                               name: AVAudioSession.routeChangeNotification, object: nil)
    }

    deinit { NotificationCenter.default.removeObserver(self) }

    private func configureSpeaker() throws {
        let s = AVAudioSession.sharedInstance()
        try s.setCategory(.playAndRecord, mode: .voiceChat, options: [.defaultToSpeaker])
        try s.setActive(true)
        try s.overrideOutputAudioPort(.speaker)
    }

    private func configureDevice() throws {
        let s = AVAudioSession.sharedInstance()
        try s.setCategory(.playAndRecord, mode: .voiceChat, options: [.allowBluetooth, .allowBluetoothA2DP])
        try s.setActive(true)
        try s.overrideOutputAudioPort(.none)
    }

    private func currentOutput() -> String {
        for o in AVAudioSession.sharedInstance().currentRoute.outputs {
            switch o.portType {
            case .builtInSpeaker: return "speaker"
            case .builtInReceiver: return "receiver"
            case .bluetoothA2DP, .bluetoothHFP, .bluetoothLE, .carAudio: return "bluetooth"
            case .headphones, .headsetMic, .usbAudio: return "wired"
            case .airPlay: return "airplay"
            default: continue
            }
        }
        return "unknown"
    }

    // Report the active output to the web UI. Deliberately does NOT re-force any route — fighting WebKit's
    // re-assertion just oscillates the audio (see ceiling note above).
    @objc private func routeChanged() {
        pending?.cancel()
        let work = DispatchWorkItem { [weak self] in
            guard let self = self else { return }
            self.notifyListeners("routeChange", data: ["output": self.currentOutput(), "native": AudioRoutePlugin.nativeTag])
        }
        pending = work
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: work)
    }

    @objc func setSpeaker(_ call: CAPPluginCall) {
        let on = call.getBool("on") ?? true
        do {
            if on { try configureSpeaker() } else { try configureDevice() }
            call.resolve(["output": currentOutput(), "native": AudioRoutePlugin.nativeTag])
        } catch {
            call.reject(error.localizedDescription)
        }
    }

    @objc func getRoute(_ call: CAPPluginCall) {
        call.resolve(["output": currentOutput(), "native": AudioRoutePlugin.nativeTag])
    }
}
