import AVFoundation
import Foundation

/// Decodes OGG Vorbis files to AVAudioPCMBuffer using stb_vorbis.
/// This enables playback of .ogg files through AVAudioEngine.
struct OGGDecoder {

    enum OGGError: LocalizedError {
        case failedToOpen(String)
        case failedToCreateBuffer
        case decodingFailed

        var errorDescription: String? {
            switch self {
            case .failedToOpen(let path): return "Failed to open OGG file: \(path)"
            case .failedToCreateBuffer: return "Failed to create audio buffer"
            case .decodingFailed: return "OGG decoding failed"
            }
        }
    }

    /// Decode an OGG file to an AVAudioPCMBuffer ready for AVAudioPlayerNode.
    static func decode(url: URL) throws -> (buffer: AVAudioPCMBuffer, format: AVAudioFormat) {
        // Open the OGG file
        var error: Int32 = 0
        guard let vorbis = stb_vorbis_open_filename(url.path, &error, nil) else {
            throw OGGError.failedToOpen(url.lastPathComponent)
        }
        defer { stb_vorbis_close(vorbis) }

        // Get file info
        let info = stb_vorbis_get_info(vorbis)
        let channels = Int(info.channels)
        let sampleRate = Double(info.sample_rate)
        let totalSamples = Int(stb_vorbis_stream_length_in_samples(vorbis))

        // Create the output format (non-interleaved float for AVAudioPlayerNode)
        guard let format = AVAudioFormat(
            commonFormat: .pcmFormatFloat32,
            sampleRate: sampleRate,
            channels: AVAudioChannelCount(channels),
            interleaved: false
        ) else {
            throw OGGError.failedToCreateBuffer
        }

        guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(totalSamples)) else {
            throw OGGError.failedToCreateBuffer
        }

        // Decode all samples — stb_vorbis gives interleaved floats,
        // so we decode to a temp buffer then deinterleave
        var tempInterleaved = [Float](repeating: 0, count: totalSamples * channels)
        let decoded = stb_vorbis_get_samples_float_interleaved(
            vorbis,
            Int32(channels),
            &tempInterleaved,
            Int32(totalSamples * channels)
        )

        if decoded <= 0 {
            throw OGGError.decodingFailed
        }

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

        buffer.frameLength = AVAudioFrameCount(decoded)
        return (buffer, format)
    }

    /// Get the duration of an OGG file in seconds.
    static func duration(url: URL) -> TimeInterval {
        var error: Int32 = 0
        guard let vorbis = stb_vorbis_open_filename(url.path, &error, nil) else { return 0 }
        defer { stb_vorbis_close(vorbis) }

        let totalSamples = stb_vorbis_stream_length_in_samples(vorbis)
        let info = stb_vorbis_get_info(vorbis)
        guard info.sample_rate > 0 else { return 0 }

        return Double(totalSamples) / Double(info.sample_rate)
    }

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

        let info = stb_vorbis_get_info(vorbis)
        let totalSamples = stb_vorbis_stream_length_in_samples(vorbis)
        let duration = info.sample_rate > 0 ? Double(totalSamples) / Double(info.sample_rate) : 0

        return (
            sampleRate: Double(info.sample_rate),
            channels: Int(info.channels),
            duration: duration
        )
    }

    /// Check if a file is an OGG Vorbis file.
    static func isOGGFile(_ url: URL) -> Bool {
        url.pathExtension.lowercased() == "ogg"
    }

    /// Convert an OGG file to a CAF file (for compatibility with AVAudioFile).
    /// Returns the URL of the converted file.
    static func convertToCAF(url: URL) throws -> URL {
        let (buffer, format) = try decode(url: url)

        let cafURL = url.deletingPathExtension().appendingPathExtension("caf")

        // Write to CAF format
        let outputFile = try AVAudioFile(
            forWriting: cafURL,
            settings: format.settings,
            commonFormat: .pcmFormatFloat32,
            interleaved: false
        )
        try outputFile.write(from: buffer)

        return cafURL
    }
}
