//
//  AliyunPCMStreamingPlayer.swift
//  suixinkan
//

import AVFoundation
import Foundation

/// TTS PCM 流式播放器协议，便于单元测试替换音频输出。
protocol AliyunPCMStreamingPlaying: AnyObject {
    /// 开始播放指定采样率的 16-bit mono PCM。
    func start(sampleRate: Double)

    /// 写入一段 16-bit little-endian mono PCM 数据。
    func write(_ data: Data)

    /// 通知播放器没有更多数据，已排空后回调。
    func drain(_ completion: @escaping () -> Void)

    /// 立即停止并清空播放队列。
    func stop()
}

/// 基于 AVAudioEngine 的 16-bit PCM 流式播放器。
final class AliyunPCMStreamingPlayer: AliyunPCMStreamingPlaying {
    private let stateQueue = DispatchQueue(label: "com.zhifly.suixinkan.aliyunNLS.pcmPlayer")
    private let engine = AVAudioEngine()
    private let playerNode = AVAudioPlayerNode()
    private var format: AVAudioFormat?
    private var scheduledBufferCount = 0
    private var draining = false
    private var drainCompletion: (() -> Void)?
    private var started = false

    func start(sampleRate: Double) {
        stateQueue.sync {
            configureSession()
            if engine.attachedNodes.contains(playerNode) == false {
                engine.attach(playerNode)
            }
            let audioFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: sampleRate, channels: 1, interleaved: false)
            format = audioFormat
            if let audioFormat {
                engine.disconnectNodeOutput(playerNode)
                engine.connect(playerNode, to: engine.mainMixerNode, format: audioFormat)
            }
            if engine.isRunning == false {
                try? engine.start()
            }
            if playerNode.isPlaying == false {
                playerNode.play()
            }
            started = true
            draining = false
            drainCompletion = nil
        }
    }

    func write(_ data: Data) {
        guard data.count >= 2 else { return }
        stateQueue.async {
            guard self.started, let format = self.format else { return }
            let buffer = Self.makePCMBuffer(from: data, format: format)
            guard let buffer else { return }
            self.scheduledBufferCount += 1
            self.playerNode.scheduleBuffer(buffer, completionCallbackType: .dataPlayedBack) { [weak self] _ in
                self?.stateQueue.async {
                    guard let self else { return }
                    self.scheduledBufferCount = max(0, self.scheduledBufferCount - 1)
                    self.completeDrainIfNeeded()
                }
            }
        }
    }

    func drain(_ completion: @escaping () -> Void) {
        stateQueue.async {
            self.draining = true
            self.drainCompletion = completion
            self.completeDrainIfNeeded()
        }
    }

    func stop() {
        stateQueue.sync {
            playerNode.stop()
            engine.stop()
            scheduledBufferCount = 0
            draining = false
            drainCompletion = nil
            started = false
        }
    }

    private func completeDrainIfNeeded() {
        guard draining, scheduledBufferCount == 0 else { return }
        let completion = drainCompletion
        drainCompletion = nil
        draining = false
        started = false
        playerNode.stop()
        completion?()
    }

    private func configureSession() {
        let session = AVAudioSession.sharedInstance()
        try? session.setCategory(.playback, options: [.allowBluetoothHFP, .mixWithOthers])
        try? session.setActive(true)
    }

    private static func makePCMBuffer(from data: Data, format: AVAudioFormat) -> AVAudioPCMBuffer? {
        let frameCount = data.count / MemoryLayout<Int16>.size
        guard frameCount > 0,
              let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(frameCount)),
              let channel = buffer.floatChannelData?[0] else {
            return nil
        }
        buffer.frameLength = AVAudioFrameCount(frameCount)
        data.withUnsafeBytes { raw in
            let bytes = raw.bindMemory(to: UInt8.self)
            for index in 0..<frameCount {
                let low = UInt16(bytes[index * 2])
                let high = UInt16(bytes[index * 2 + 1]) << 8
                let sample = Int16(bitPattern: high | low)
                channel[index] = Float(sample) / Float(Int16.max)
            }
        }
        return buffer
    }
}
