#if !DISABLE_OPUS
import AVFoundation
import Foundation

/// Decodes OGG Opus files using libopusfile (compiled for iOS ARM64).
struct OpusDecoder {

    enum OpusError: LocalizedError {
        case failedToOpen(String, Int32)
        case failedToCreateBuffer
        case decodingFailed

        var errorDescription: String? {
            switch self {
            case .failedToOpen(let name, let code): return "Failed to open Opus file '\(name)' (error \(code))"
            case .failedToCreateBuffer: return "Failed to create audio buffer"
            case .decodingFailed: return "Opus decoding failed"
            }
        }
    }

    static func isOpusFile(_ url: URL) -> Bool {
        url.pathExtension.lowercased() == "opus"
    }

    /// Decode an OGG Opus file to an AVAudioPCMBuffer.
    static func decode(url: URL) throws -> (buffer: AVAudioPCMBuffer, format: AVAudioFormat) {
        var error: Int32 = 0
        guard let opusFile = op_open_file(url.path, &error) else {
            throw OpusError.failedToOpen(url.lastPathComponent, error)
        }
        defer { op_free(opusFile) }

        let channels = Int(op_channel_count(opusFile, -1))
        let totalSamples = op_pcm_total(opusFile, -1)
        let sampleRate: Double = 48000 // Opus always decodes at 48kHz

        guard let format = AVAudioFormat(
            commonFormat: .pcmFormatFloat32,
            sampleRate: sampleRate,
            channels: AVAudioChannelCount(channels),
            interleaved: false
        ) else {
            throw OpusError.failedToCreateBuffer
        }

        let frameCapacity = totalSamples > 0 ? AVAudioFrameCount(totalSamples) : AVAudioFrameCount(sampleRate * 600)
        guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: frameCapacity) else {
            throw OpusError.failedToCreateBuffer
        }

        // Decode in chunks — op_read_float gives interleaved float samples
        let chunkSize = 5760 * channels // 120ms at 48kHz
        var tempInterleaved = [Float](repeating: 0, count: chunkSize)
        var totalDecoded: Int64 = 0

        while true {
            let samplesRead = op_read_float(opusFile, &tempInterleaved, Int32(chunkSize / channels), nil)
            if samplesRead <= 0 { break }

            let frameCount = Int(samplesRead)

            // Deinterleave into the PCM buffer's channel pointers
            for frame in 0..<frameCount {
                for ch in 0..<channels {
                    let bufferOffset = Int(totalDecoded) + frame
                    if bufferOffset < Int(frameCapacity) {
                        buffer.floatChannelData![ch][bufferOffset] = tempInterleaved[frame * channels + ch]
                    }
                }
            }

            totalDecoded += Int64(frameCount)
        }

        guard totalDecoded > 0 else {
            throw OpusError.decodingFailed
        }

        buffer.frameLength = AVAudioFrameCount(min(totalDecoded, Int64(frameCapacity)))
        return (buffer, format)
    }

    /// Get duration of an Opus file in seconds.
    static func duration(url: URL) -> TimeInterval {
        var error: Int32 = 0
        guard let opusFile = op_open_file(url.path, &error) else { return 0 }
        defer { op_free(opusFile) }

        let totalSamples = op_pcm_total(opusFile, -1)
        return Double(totalSamples) / 48000.0
    }

    /// Get basic info about an Opus file.
    static func fileInfo(url: URL) -> (sampleRate: Double, channels: Int, duration: TimeInterval)? {
        var error: Int32 = 0
        guard let opusFile = op_open_file(url.path, &error) else { return nil }
        defer { op_free(opusFile) }

        let channels = Int(op_channel_count(opusFile, -1))
        let totalSamples = op_pcm_total(opusFile, -1)
        let duration = Double(totalSamples) / 48000.0

        return (sampleRate: 48000, channels: channels, duration: duration)
    }
}
#endif // !DISABLE_OPUS
