import ExpoModulesCore
import AVFoundation
import SoundAnalysis

public class ExpoSoundAnalysisModule: Module {
    private var audioEngine: AVAudioEngine?
    private var streamAnalyzer: SNAudioStreamAnalyzer?

    // Module definition
    public func definition() -> ModuleDefinition {
        Name("ExpoSoundAnalysis")

        Events("onSoundDetected")

        Function("startAnalysis") { () -> Void in
            do {
                try self.startAudioEngine()
            } catch {
                throw error
            }
        }

        Function("stopAnalysis") { () -> Void in
            self.stopAudioEngine()
        }

        AsyncFunction("analyzeFile") { (uri: String) -> [[String: Any]] in
            try await self.analyzeAudioFile(uri)
        }
    }

private func analyzeAudioFile(_ uri: String) async throws -> [[String: Any]] {
    guard let url = URL(string: uri), FileManager.default.fileExists(atPath: url.path) else {
        throw Exception(file: "Invalid or non-existent URI")
    }

    print("Analyzing file at URL: \(url)")

    let analyzer = try SNAudioFileAnalyzer(url: url)
    let request = try SNClassifySoundRequest(classifierIdentifier: SNClassifierIdentifier.version1)
    print("Request created successfully.")

    var results: [[String: Any]] = []

    let semaphore = DispatchSemaphore(value: 0) // To wait for completion
    let observer = ResultsObserver { event in
        print("Classification: \(event.classification), Confidence: \(event.confidence), Timestamp: \(event.timestamp)")
        results.append([
            "type": event.classification,
            "confidence": event.confidence,
            "timestamp": event.timestamp
        ])
        semaphore.signal() // Notify that we got a result
    }

    do {
        try analyzer.add(request, withObserver: observer)
        print("Request added successfully.")

        // Wait for the analysis to complete
        if semaphore.wait(timeout: .now() + 20) == .timedOut {
            print("Analysis timed out.")
        }
    } catch {
        print("Error during analysis: \(error.localizedDescription)")
        throw error
    }

    print("Analysis results: \(results)")
    return results
}





    private func startAudioEngine() throws {
        let audioSession = AVAudioSession.sharedInstance()
        try audioSession.setCategory(.record, mode: .default)
        try audioSession.setActive(true)

        audioEngine = AVAudioEngine()
        guard let inputNode = audioEngine?.inputNode else {
            throw Exception(file: "Audio engine has no input node")
        }

        let format = inputNode.outputFormat(forBus: 0)
        streamAnalyzer = SNAudioStreamAnalyzer(format: format)

        let request = try SNClassifySoundRequest(classifierIdentifier: SNClassifierIdentifier.version1)
        try streamAnalyzer?.add(request, withObserver: ResultsObserver { [weak self] event in
            self?.sendEvent("onSoundDetected", [
                "type": event.classification,
                "confidence": event.confidence,
                "timestamp": event.timestamp
            ])
        })

        inputNode.installTap(onBus: 0, bufferSize: 8192, format: format) { [weak self] buffer, time in
            try? self?.streamAnalyzer?.analyze(buffer, atAudioFramePosition: time.sampleTime)
        }

        try audioEngine?.start()
    }

    private func stopAudioEngine() {
        audioEngine?.stop()
        audioEngine?.inputNode.removeTap(onBus: 0)
        streamAnalyzer = nil
    }
}

class ResultsObserver: NSObject, SNResultsObserving {
    private let callback: (SoundEvent) -> Void

    init(callback: @escaping (SoundEvent) -> Void) {
        self.callback = callback
    }

    func request(_ request: SNRequest, didProduce result: SNResult) {
        guard let result = result as? SNClassificationResult else {
            print("No classification result.")
            return
        }

        for classification in result.classifications {
            print("Classification: \(classification.identifier), Confidence: \(classification.confidence)")
            if classification.confidence > 0.5 {
                callback(SoundEvent(
                    classification: classification.identifier,
                    confidence: classification.confidence,
                    timestamp: result.timeRange.start.seconds
                ))
            }
        }
    }
}

struct SoundEvent {
    let classification: String
    let confidence: Double
    let timestamp: Double
}
