import AVFoundation
import Foundation

enum DubbingCompositorError: LocalizedError {
    case inputReadFailed(String?)
    case outputWriteFailed(String?)
    case convertFailed(String?)

    var errorDescription: String? {
        switch self {
        case .inputReadFailed(let detail):
            return detail.map { "无法读取录音音频（\($0)）" } ?? "无法读取录音音频"
        case .outputWriteFailed(let detail):
            return detail.map { "无法写入合成音频（\($0)）" } ?? "无法写入合成音频"
        case .convertFailed(let detail):
            return detail.map { "无法转换音频格式（\($0)）" } ?? "无法转换音频格式"
        }
    }
}

enum DubbingCompositor {
    /// 合并多段录音为一段 16kHz 单声道 AAC（m4a）。
    ///
    /// 不使用 AVMutableComposition + AVAssetExportSession：iOS 26 / macOS 26 上
    /// AVAssetExportSession 对纯音频资源导出会失败（AVError -11800，底层 'fmt?'），
    /// AVAudioFile 读回 AAC 也会失败（CheckClientFormatSet / 'fmt?'）。
    /// 因此传入录音时保留的原始 PCM（caf），解码拼接后直接编码 AAC，
    /// 与录音转码走同一条已验证可用的链路。
    static func merge(urls: [URL], outputURL: URL) async throws {
        do {
            try mergeSync(urls: urls, outputURL: outputURL)
        } catch {
            try? FileManager.default.removeItem(at: outputURL)
            throw error
        }
    }

    private static func mergeSync(urls: [URL], outputURL: URL) throws {
        try? FileManager.default.removeItem(at: outputURL)
        let settings: [String: Any] = [
            AVFormatIDKey: kAudioFormatMPEG4AAC,
            AVSampleRateKey: 16_000,
            AVNumberOfChannelsKey: 1,
        ]
        let outputFile: AVAudioFile
        do {
            outputFile = try AVAudioFile(forWriting: outputURL, settings: settings)
        } catch {
            throw DubbingCompositorError.outputWriteFailed(error.localizedDescription)
        }
        let targetFormat = outputFile.processingFormat
        for url in urls {
            let inputFile: AVAudioFile
            do {
                inputFile = try AVAudioFile(forReading: url)
            } catch {
                throw DubbingCompositorError.inputReadFailed(error.localizedDescription)
            }
            guard inputFile.length > 0 else {
                throw DubbingCompositorError.inputReadFailed("录音为空")
            }
            let inputFormat = inputFile.processingFormat
            if inputFormat.sampleRate == targetFormat.sampleRate,
               inputFormat.channelCount == targetFormat.channelCount {
                try appendStream(from: inputFile, to: outputFile)
            } else {
                try appendConverted(from: inputFile, to: outputFile, targetFormat: targetFormat)
            }
        }
    }

    private static func appendStream(from inputFile: AVAudioFile, to outputFile: AVAudioFile) throws {
        var remaining = inputFile.length
        while remaining > 0 {
            let frames = AVAudioFrameCount(min(remaining, 8192))
            guard let buffer = AVAudioPCMBuffer(pcmFormat: inputFile.processingFormat, frameCapacity: frames) else {
                throw DubbingCompositorError.convertFailed("无法分配解码缓冲")
            }
            do {
                try inputFile.read(into: buffer)
            } catch {
                throw DubbingCompositorError.inputReadFailed(error.localizedDescription)
            }
            guard buffer.frameLength > 0 else {
                throw DubbingCompositorError.inputReadFailed("录音文件读取出错（读取到空帧）")
            }
            do {
                try outputFile.write(from: buffer)
            } catch {
                throw DubbingCompositorError.outputWriteFailed(error.localizedDescription)
            }
            remaining -= AVAudioFramePosition(buffer.frameLength)
        }
    }

    private static func appendConverted(
        from inputFile: AVAudioFile,
        to outputFile: AVAudioFile,
        targetFormat: AVAudioFormat
    ) throws {
        guard let converter = AVAudioConverter(from: inputFile.processingFormat, to: targetFormat) else {
            throw DubbingCompositorError.convertFailed("无法创建格式转换器")
        }
        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)
                guard buffer.frameLength > 0 else {
                    outStatus.pointee = .endOfStream
                    return nil
                }
                outStatus.pointee = .haveData
                return buffer
            } catch {
                outStatus.pointee = .noDataNow
                return nil
            }
        }
        guard let outputBuffer = AVAudioPCMBuffer(pcmFormat: targetFormat, frameCapacity: 8192) else {
            throw DubbingCompositorError.convertFailed("无法分配转码缓冲")
        }
        while true {
            var error: NSError?
            let status = converter.convert(to: outputBuffer, error: &error, withInputFrom: inputBlock)
            switch status {
            case .haveData:
                do {
                    try outputFile.write(from: outputBuffer)
                } catch {
                    throw DubbingCompositorError.outputWriteFailed(error.localizedDescription)
                }
            case .inputRanDry, .endOfStream:
                return
            case .error:
                if let error {
                    throw DubbingCompositorError.convertFailed(error.localizedDescription)
                }
                throw DubbingCompositorError.convertFailed(nil)
            default:
                return
            }
        }
    }
}
