import Foundation
import AVFoundation

/// Represents a detected silent interval within an audio recording.
public struct SilenceRange: Codable, Hashable, Sendable {
    public let start: TimeInterval // in seconds
    public let end: TimeInterval   // in seconds
    
    public var duration: TimeInterval {
        end - start
    }
    
    public init(start: TimeInterval, end: TimeInterval) {
        self.start = start
        self.end = end
    }
}

/// Playback analysis generated in one pass over the decoded audio.
public struct AudioAnalysisResult: Sendable {
    public let waveformSamples: [Float]
    public let silentRanges: [SilenceRange]

    public init(waveformSamples: [Float], silentRanges: [SilenceRange]) {
        self.waveformSamples = waveformSamples
        self.silentRanges = silentRanges
    }
}

public final class SilenceDetector: Sendable {
    /// Detects silent ranges in the specified audio file.
    /// - Parameters:
    ///   - audioURL: Local file URL of the audio.
    ///   - thresholdDB: Threshold in decibels (e.g. -40.0 dB). Sounds below this are considered silent.
    ///   - minDuration: Minimum consecutive duration in seconds to qualify as a silent segment.
    /// - Returns: An array of detected SilenceRange objects.
    public static func detectSilence(
        in audioURL: URL,
        thresholdDB: Float = -40.0,
        minDuration: TimeInterval = 2.0
    ) async -> [SilenceRange] {
        await analyze(
            audioURL,
            silenceThresholdDB: thresholdDB,
            minimumSilenceDuration: minDuration
        ).silentRanges
    }

    /// Extracts waveform levels and silence ranges together to avoid decoding twice.
    /// Waveform samples use the same 50ms cadence and dB normalization as live recording.
    public static func analyze(
        _ audioURL: URL,
        silenceThresholdDB: Float = -40.0,
        minimumSilenceDuration: TimeInterval = 2.0
    ) async -> AudioAnalysisResult {
        guard let validURL = AudioPathHelper.resolveURL(for: audioURL.path) else {
            print("[SilenceDetector] File not found or empty path: \(audioURL.path).")
            return AudioAnalysisResult(waveformSamples: [], silentRanges: [])
        }
        
        let task = Task.detached(priority: .userInitiated) { () -> AudioAnalysisResult in
            guard let audioFile = try? AVAudioFile(forReading: validURL) else {
                return AudioAnalysisResult(waveformSamples: [], silentRanges: [])
            }
            
            let format = audioFile.processingFormat
            let sampleRate = format.sampleRate
            guard sampleRate > 0 else {
                return AudioAnalysisResult(waveformSamples: [], silentRanges: [])
            }
            
            // Buffer size of ~1.0 second
            let bufferSize = AVAudioFrameCount(sampleRate)
            guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: bufferSize) else {
                return AudioAnalysisResult(waveformSamples: [], silentRanges: [])
            }
            
            var waveformSamples: [Float] = []
            waveformSamples.reserveCapacity(Int(Double(audioFile.length) / sampleRate / 0.05) + 1)
            var silentRanges: [SilenceRange] = []
            var isSilent = false
            var silenceStart: TimeInterval = 0
            
            // Match RealAudioRecorder's 50ms metering cadence.
            let subChunkSize = max(1, Int(sampleRate * 0.05))
            
            do {
                while audioFile.framePosition < audioFile.length {
                    if Task.isCancelled {
                        return AudioAnalysisResult(waveformSamples: [], silentRanges: [])
                    }

                    let framesToRead = min(bufferSize, AVAudioFrameCount(audioFile.length - audioFile.framePosition))
                    if framesToRead <= 0 { break }
                    
                    try audioFile.read(into: buffer, frameCount: framesToRead)
                    
                    guard let floatChannelData = buffer.floatChannelData else { continue }
                    let channelData = floatChannelData[0]
                    let frameLength = Int(buffer.frameLength)
                    
                    var offset = 0
                    while offset < frameLength {
                        let currentSubChunkSize = min(subChunkSize, frameLength - offset)
                        if currentSubChunkSize <= 0 { break }
                        
                        var sum: Float = 0
                        for i in 0..<currentSubChunkSize {
                            let sample = channelData[offset + i]
                            sum += sample * sample
                        }
                        
                        let rms = sqrt(sum / Float(currentSubChunkSize))
                        let db = rms > 0 ? 20 * log10(rms) : -100.0
                        waveformSamples.append(
                            AudioLevelNormalizer.normalizedLevel(decibels: db)
                        )
                        
                        // Calculate absolute time of this sub-chunk
                        let absoluteFramePosition = audioFile.framePosition - Int64(frameLength) + Int64(offset)
                        let timeInSeconds = Double(absoluteFramePosition) / sampleRate
                        
                        let isChunkSilent = db < silenceThresholdDB
                        
                        if isChunkSilent {
                            if !isSilent {
                                isSilent = true
                                silenceStart = timeInSeconds
                            }
                        } else {
                            if isSilent {
                                isSilent = false
                                let silenceEnd = timeInSeconds
                                if silenceEnd - silenceStart >= minimumSilenceDuration {
                                    silentRanges.append(SilenceRange(start: silenceStart, end: silenceEnd))
                                }
                            }
                        }
                        
                        offset += subChunkSize
                    }
                }
                
                // If still silent at the end of the file
                if isSilent {
                    let silenceEnd = Double(audioFile.length) / sampleRate
                    if silenceEnd - silenceStart >= minimumSilenceDuration {
                        silentRanges.append(SilenceRange(start: silenceStart, end: silenceEnd))
                    }
                }
            } catch {
                print("[SilenceDetector] Error reading audio file: \(error.localizedDescription)")
            }
            
            print(
                "[SilenceDetector] Analysis complete. Generated \(waveformSamples.count) waveform samples and found \(silentRanges.count) silence ranges."
            )
            return AudioAnalysisResult(
                waveformSamples: waveformSamples,
                silentRanges: silentRanges
            )
        }
        return await task.value
    }
}
