import AVFoundation
import Foundation

enum AudioRecorderSessionError: LocalizedError {
    case inputUnavailable
    case startFailed
    case conversionFailed

    var errorDescription: String? {
        switch self {
        case .inputUnavailable:
            return "未检测到麦克风输入"
        case .startFailed:
            return "无法开始录制：请确认麦克风未被其他应用占用后重试"
        case .conversionFailed:
            return "录音文件转码失败"
        }
    }
}

/// 基于 AVAudioEngine 的录音会话。
///
/// AVAudioRecorder 在 iOS 26 真机上存在 record() 恒返回 false 的已知问题
/// （Apple 开发者论坛多个报告），因此这里改用 AVAudioEngine 采集 PCM，
/// 停止录音时再转码为 16kHz 单声道 AAC（m4a），与服务端评测接口保持一致。
final class AudioRecorderSession {
    private let engine = AVAudioEngine()
    private let outputURL: URL
    private let pcmURL: URL
    private var inputFile: AVAudioFile?
    private var tapInstalled = false

    private(set) var isRunning = false
    private(set) var recordDuration: TimeInterval = 0

    /// 本次录音的原始 PCM 文件（caf），停止录音后仍保留，
    /// 供配音合成直接合并，避免在 iOS 26 上读回 AAC（AVAudioFile 存在 'fmt?' 问题）。
    var rawRecordingURL: URL { pcmURL }

    init(outputURL: URL) {
        self.outputURL = outputURL
        let stamp = Int(Date().timeIntervalSince1970 * 1000)
        self.pcmURL = FileManager.default.temporaryDirectory
            .appendingPathComponent("raw-\(stamp).caf")
    }

    /// 激活音频会话并开始采集。失败时抛出错误，可在重新激活会话后重试。
    func start() throws {
        let session = AVAudioSession.sharedInstance()
        try session.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker])
        try session.setActive(true)
        guard session.isInputAvailable else {
            throw AudioRecorderSessionError.inputUnavailable
        }

        let input = engine.inputNode
        if !tapInstalled {
            let inputFormat = input.outputFormat(forBus: 0)
            guard inputFormat.sampleRate > 0, inputFormat.channelCount > 0 else {
                throw AudioRecorderSessionError.inputUnavailable
            }
            let file = try AVAudioFile(forWriting: pcmURL, settings: inputFormat.settings)
            inputFile = file
            input.installTap(onBus: 0, bufferSize: 4096, format: inputFormat) { [weak self] buffer, _ in
                guard let self = self else { return }
                do {
                    try self.inputFile?.write(from: buffer)
                } catch {
                    // 单个缓冲写入失败时忽略，继续录音
                }
            }
            tapInstalled = true
        }
        engine.prepare()
        do {
            try engine.start()
        } catch {
            throw AudioRecorderSessionError.startFailed
        }
        isRunning = true
    }

    /// 停止录音，将 PCM 转码为 m4a 写入 outputURL。
    func stop() throws {
        guard isRunning else { return }
        engine.inputNode.removeTap(onBus: 0)
        engine.stop()
        isRunning = false
        tapInstalled = false
        inputFile = nil
        try finalizeToAAC()
    }

    /// 放弃本次录音并清理临时文件。
    func cancel() {
        if isRunning {
            engine.inputNode.removeTap(onBus: 0)
            engine.stop()
            isRunning = false
            tapInstalled = false
            inputFile = nil
        }
        try? FileManager.default.removeItem(at: pcmURL)
        try? FileManager.default.removeItem(at: outputURL)
    }

    private func finalizeToAAC() throws {
        let inputFile = try AVAudioFile(forReading: pcmURL)
        recordDuration = Double(inputFile.length) / inputFile.processingFormat.sampleRate
        let outputSettings: [String: Any] = [
            AVFormatIDKey: kAudioFormatMPEG4AAC,
            AVSampleRateKey: 16_000,
            AVNumberOfChannelsKey: 1,
        ]
        let outputFile = try AVAudioFile(forWriting: outputURL, settings: outputSettings)
        guard let converter = AVAudioConverter(from: inputFile.processingFormat, to: outputFile.processingFormat) else {
            throw AudioRecorderSessionError.conversionFailed
        }
        let inputBlock: AVAudioConverterInputBlock = { inNumPackets, outStatus in
            if inputFile.framePosition >= inputFile.length {
                outStatus.pointee = .endOfStream
                return nil
            }
            let remaining = inputFile.length - inputFile.framePosition
            let frames = AVAudioFrameCount(min(inNumPackets, AVAudioFrameCount(remaining)))
            guard frames > 0,
                  let buffer = AVAudioPCMBuffer(pcmFormat: inputFile.processingFormat, frameCapacity: frames) else {
                outStatus.pointee = .noDataNow
                return nil
            }
            do {
                try inputFile.read(into: buffer)
                outStatus.pointee = .haveData
                return buffer
            } catch {
                outStatus.pointee = .noDataNow
                return nil
            }
        }
        let capacity = AVAudioFrameCount(min(4096, max(1, Int(inputFile.length))))
        guard let outputBuffer = AVAudioPCMBuffer(pcmFormat: outputFile.processingFormat, frameCapacity: capacity) else {
            throw AudioRecorderSessionError.conversionFailed
        }
        while true {
            var conversionError: NSError?
            let status = converter.convert(to: outputBuffer, error: &conversionError, withInputFrom: inputBlock)
            switch status {
            case .haveData:
                try outputFile.write(from: outputBuffer)
            case .inputRanDry, .endOfStream:
                return
            case .error:
                throw conversionError ?? AudioRecorderSessionError.conversionFailed
            default:
                return
            }
        }
    }
}
