import AVFAudio
import Combine
import QuartzCore
import os

// MARK: - VoiceMessagePlayer

/// Singleton audio player for voice messages in the message list.
/// Only one voice message plays at a time — tapping another stops the current.
/// Uses AVAudioPlayer for local file playback with display link for progress.
@MainActor
final class VoiceMessagePlayer: ObservableObject {

    static let shared = VoiceMessagePlayer()

    private let logger = Logger(subsystem: "com.rosetta.messenger", category: "VoicePlayer")

    // MARK: - Published State

    @Published private(set) var currentMessageId: String?
    @Published private(set) var isPlaying = false
    @Published private(set) var progress: Double = 0
    @Published private(set) var currentTime: TimeInterval = 0
    @Published private(set) var duration: TimeInterval = 0

    /// Called with the finished messageId right before `stop()` resets state.
    /// Used by NativeMessageListController for sequential voice autoplay.
    var onPlaybackCompleted: ((String) -> Void)?

    // MARK: - Private

    private var audioPlayer: AVAudioPlayer?
    private var displayLink: CADisplayLink?
    private var displayLinkTarget: DisplayLinkProxy?

    private init() {}

    // MARK: - Public API

    /// Play a voice message. Stops any currently playing message first.
    func play(messageId: String, fileURL: URL) {
        // If same message is playing, toggle pause
        if currentMessageId == messageId, isPlaying {
            pause()
            return
        }

        // Stop previous playback
        stop()

        do {
            try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
            try AVAudioSession.sharedInstance().setActive(true)

            let player = try AVAudioPlayer(contentsOf: fileURL)
            player.prepareToPlay()
            player.play()

            audioPlayer = player
            currentMessageId = messageId
            isPlaying = true
            duration = player.duration
            startDisplayLink()

            logger.info("[VoicePlayer] Playing \(messageId.prefix(8))")
        } catch {
            logger.error("[VoicePlayer] Failed: \(error.localizedDescription)")
            stop()
        }
    }

    func pause() {
        audioPlayer?.pause()
        isPlaying = false
        stopDisplayLink()
    }

    func resume() {
        guard audioPlayer != nil else { return }
        audioPlayer?.play()
        isPlaying = true
        startDisplayLink()
    }

    func togglePlayPause() {
        if isPlaying { pause() } else { resume() }
    }

    func stop() {
        audioPlayer?.stop()
        audioPlayer = nil
        currentMessageId = nil
        isPlaying = false
        progress = 0
        currentTime = 0
        duration = 0
        stopDisplayLink()

        try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
    }

    /// Seek to a fraction of the duration (0..1).
    func seek(to fraction: Double) {
        guard let player = audioPlayer else { return }
        let target = fraction * player.duration
        player.currentTime = target
        updateProgress()
    }

    // MARK: - Display Link

    private func startDisplayLink() {
        guard displayLink == nil else { return }
        let proxy = DisplayLinkProxy { [weak self] in self?.updateProgress() }
        let link = CADisplayLink(target: proxy, selector: #selector(DisplayLinkProxy.tick))
        link.add(to: .main, forMode: .common)
        displayLink = link
        displayLinkTarget = proxy
    }

    private func stopDisplayLink() {
        displayLink?.invalidate()
        displayLink = nil
        displayLinkTarget = nil
    }

    private func updateProgress() {
        guard let player = audioPlayer else { return }
        if !player.isPlaying && isPlaying {
            // Playback ended naturally — notify before resetting state
            let finishedId = currentMessageId
            stop()
            if let finishedId {
                onPlaybackCompleted?(finishedId)
            }
            return
        }
        currentTime = player.currentTime
        progress = player.duration > 0 ? player.currentTime / player.duration : 0
    }
}

// MARK: - DisplayLinkProxy

private final class DisplayLinkProxy: NSObject {
    let callback: () -> Void
    init(_ callback: @escaping () -> Void) { self.callback = callback }
    @objc func tick() { callback() }
}
