import Foundation
import Speech
import AVFoundation
import SwiftUI
import Combine

class SpeechRecognizer: ObservableObject {
    @Published var transcript: String = ""
    @Published var isRecording: Bool = false
    @Published var error: String?
    
    // Callback for when a final result is ready to be broadcast
    var onFinalResult: ((String) -> Void)?
    // Callback for partial results
    var onPartialResult: ((String) -> Void)?
    
    private let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))
    private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
    private var recognitionTask: SFSpeechRecognitionTask?
    private let audioEngine = AVAudioEngine()
    
    private var silenceTimer: Timer?
    
    init() {
        requestAuthorization()
    }
    
    private func requestAuthorization() {
        SFSpeechRecognizer.requestAuthorization { authStatus in
            DispatchQueue.main.async {
                switch authStatus {
                case .authorized:
                    break
                case .denied:
                    self.error = "Speech recognition authorization denied"
                case .restricted:
                    self.error = "Speech recognition restricted on this device"
                case .notDetermined:
                    self.error = "Speech recognition not yet authorized"
                @unknown default:
                    self.error = "Unknown authorization status"
                }
            }
        }
    }
    
    func startRecording() {
        // If already recording, we might be calling this to restart, so we shouldn't necessarily just return.
        // But for the public API toggle, we check `isRecording`.
        // To support internal restarts, we might want to separate the logic.
        // However, `stopRecording` sets `isRecording = false`.
        
        if isRecording {
            stopRecording()
            // If the user tapped the button, we stop.
            // If we are auto-restarting, we want to start again immediately.
            // Let's rely on the caller or proper state management.
            // If this is called from the UI toggle, we stop.
            return
        }
        
        startRecordingInternal()
    }
    
    private func startRecordingInternal() {
        // Reset state
        lastFlushedTranscript = ""
        currentFullTranscription = ""
        
        // Cancel previous task if any
        if recognitionTask != nil {
            recognitionTask?.cancel()
            recognitionTask = nil
        }
        
        let audioSession = AVAudioSession.sharedInstance()
        do {
            try audioSession.setCategory(.record, mode: .measurement, options: .duckOthers)
            try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
        } catch {
            self.error = "Audio session setup failed: \(error.localizedDescription)"
            return
        }
        
        recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
        
        guard let recognitionRequest = recognitionRequest else {
            self.error = "Unable to create recognition request"
            return
        }
        
        // Check network status to determine recognition mode
        if NetworkMonitor.shared.isConnected {
             // Force on-device recognition to false as per PRD for higher accuracy when online
            recognitionRequest.requiresOnDeviceRecognition = false
            print("SpeechRecognizer: Using server-based recognition (Online)")
        } else {
            // Fallback to on-device recognition when offline
            recognitionRequest.requiresOnDeviceRecognition = true
            print("SpeechRecognizer: Using on-device recognition (Offline)")
        }
        recognitionRequest.shouldReportPartialResults = true
        
        // Keep a local reference to the request to check against stale callbacks
        let currentRequest = recognitionRequest
        
        let inputNode = audioEngine.inputNode
        
        // Remove any existing tap to prevent "Tap already installed" crash
        inputNode.removeTap(onBus: 0)
        
        recognitionTask = speechRecognizer?.recognitionTask(with: recognitionRequest) { [weak self] result, error in
            guard let self = self else { return }
            
            // Safety Check: Ignore callbacks from old/stale requests
            // This prevents a previous session's error/completion from stopping the NEW session
            guard self.recognitionRequest === currentRequest else {
                return
            }
            
            var isFinal = false
            
        if let result = result {
                DispatchQueue.main.async {
                    let fullText = result.bestTranscription.formattedString
                    self.currentFullTranscription = fullText
                    
                    // Robust Delta Calculation
                    var newText = fullText
                    
                    if !self.lastFlushedTranscript.isEmpty {
                        if fullText.hasPrefix(self.lastFlushedTranscript) {
                            // Perfect match logic
                            newText = String(fullText.dropFirst(self.lastFlushedTranscript.count))
                        } else if fullText.lowercased().hasPrefix(self.lastFlushedTranscript.lowercased()) {
                            // Case-insensitive match logic
                            newText = String(fullText.dropFirst(self.lastFlushedTranscript.count))
                        } else {
                            // Fallback: History rewritten (punctuation/spelling change). 
                            // Blindly drop the length of the old text to avoid duplicating history.
                            let dropCount = min(fullText.count, self.lastFlushedTranscript.count)
                            newText = String(fullText.dropFirst(dropCount))
                        }
                    }
                    
                    self.transcript = newText.trimmingCharacters(in: .whitespacesAndNewlines)
                    
                    // Trigger partial result callback
                    self.onPartialResult?(self.transcript)
                    
                    // Reset silence timer on every new result
                    self.resetSilenceTimer()
                }
                isFinal = result.isFinal
                
                // If it's a final result, trigger the callback to broadcast
                if isFinal {
                    DispatchQueue.main.async {
                        // Send the DELTA (self.transcript) not the full result
                        if !self.transcript.isEmpty {
                            self.onFinalResult?(self.transcript)
                        }
                        self.silenceTimer?.invalidate()
                    }
                }
            }
            
            if error != nil || isFinal {
                // Perform cleanup
                self.audioEngine.stop()
                inputNode.removeTap(onBus: 0)
                self.recognitionRequest = nil
                self.recognitionTask = nil
                self.silenceTimer?.invalidate() // Safety invalidation
                
                if error != nil {
                     DispatchQueue.main.async {
                        self.isRecording = false
                    }
                }
            }
        }
        
        let recordingFormat = inputNode.outputFormat(forBus: 0)
        inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { buffer, when in
            self.recognitionRequest?.append(buffer)
        }
        
        audioEngine.prepare()
        
        do {
            try audioEngine.start()
            isRecording = true
            transcript = "Listening..."
        } catch {
            self.error = "Audio engine start failed: \(error.localizedDescription)"
        }
    }
    
    func stopRecording(shouldUpdateState: Bool = true) {
        silenceTimer?.invalidate()
        
        // Manual Flush: If we have pending text, send it now to guarantee it's not lost.
        if !transcript.isEmpty && transcript != "Listening..." {
            onFinalResult?(transcript)
            transcript = ""
        } else {
            // Also clear if it was just "Listening..."
            transcript = ""
        }
        
        // Always trigger an empty partial result to clear the UI on the receiver side
        onPartialResult?("")
        
        audioEngine.stop()
        // We use cancel() here instead of endAudio() because we just handled the final result manually.
        // This prevents double-sending if endAudio() were to trigger a final callback later.
        recognitionTask?.cancel()
        
        // Deactivate Audio Session to release Bluetooth/Mic resources
        // This is critical to prevent interference with MultipeerConnectivity
        let audioSession = AVAudioSession.sharedInstance()
        do {
            try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
        } catch {
             print("Failed to deactivate audio session: \(error)")
        }
        
        if shouldUpdateState {
            isRecording = false
        }
    }
    
    private func resetSilenceTimer() {
        silenceTimer?.invalidate()
        silenceTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { [weak self] _ in
            self?.handleSilence()
        }
    }
    
    private var lastFlushedTranscript: String = ""
    private var currentFullTranscription: String = ""
    
    private func handleSilence() {
        // Soft flush: Send what we have, clear local view, but keep engine running
        guard !transcript.isEmpty && transcript != "Listening..." else { return }
        
        // Send the final result
        onFinalResult?(transcript)
        
        // Mark the current *full* text as "flushed" so we ignore it in future updates
        // We use the full string that the recognizer currently has
        lastFlushedTranscript = currentFullTranscription
        
        // Clear local transcript view
        transcript = ""
        onPartialResult?("") 
        
        print("Silence detected, flushed text. Engine continuing...")
    }
}

