import AVFoundation

/// Each prompt owns a short audio session. Cancellation invalidates its completion.
@MainActor
final class CalibrationSpeaker: NSObject, AVSpeechSynthesizerDelegate {
    private let synthesizer = AVSpeechSynthesizer()
    private var utterance: AVSpeechUtterance?
    private var completion: (() -> Void)?
    private var ownsAudio = false
    var diagnostic: ((String) -> Void)?
    var audioDescription: String {
        let audio = AVAudioSession.sharedInstance()
        let outputs = audio.currentRoute.outputs.map { $0.portType.rawValue }.joined(separator: ", ")
        return "app-session output=\(outputs); volume=\(audio.outputVolume); mode=\(audio.mode.rawValue); system-managed-speech=\(!synthesizer.usesApplicationAudioSession)"
    }
    override init() {
        super.init(); synthesizer.delegate = self
        #if os(watchOS)
        // Let watchOS own the speech route and lifetime rather than repeatedly
        // activating/deactivating the app's media session between short prompts.
        synthesizer.usesApplicationAudioSession = false
        #endif
    }
    @discardableResult func say(_ text: String, completion: (() -> Void)? = nil) -> Bool {
        cancel()
        #if !os(watchOS)
        do {
            let audio = AVAudioSession.sharedInstance()
            try audio.setCategory(.playback, mode: .spokenAudio, options: [.duckOthers, .interruptSpokenAudioAndMixWithOthers])
            try audio.setActive(true)
            ownsAudio = true
        } catch { diagnostic?("activation failed: \(error.localizedDescription)"); return false }
        #endif
        diagnostic?("requested: \(audioDescription)")
        #if os(watchOS)
        let spokenText = text.replacingOccurrences(of: "phone", with: "Watch")
        #else
        let spokenText = text
        #endif
        let next = AVSpeechUtterance(string: spokenText)
        next.rate = 0.48
        utterance = next; self.completion = completion
        synthesizer.speak(next)
        return true
    }
    func cancel() {
        utterance = nil; completion = nil
        synthesizer.stopSpeaking(at: .immediate)
        releaseAudio()
    }
    private func releaseAudio() {
        guard ownsAudio else { return }
        ownsAudio = false
        try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
    }
    nonisolated func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didStart utterance: AVSpeechUtterance) {
        Task { @MainActor [weak self] in
            guard let self, self.utterance === utterance else { return }
            self.diagnostic?("started: \(self.audioDescription)")
        }
    }
    nonisolated func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
        Task { @MainActor [weak self] in
            guard let self, self.utterance === utterance else { return }
            self.diagnostic?("finished: \(self.audioDescription)")
            let done = self.completion
            self.utterance = nil; self.completion = nil; self.releaseAudio()
            done?()
        }
    }
}
