import SwiftUI
import Observation
import AVFoundation

// MARK: - PlaybackViewModel

/// Manages audio playback state, timeline scrubbing, and event filtering
/// for session detail/review screens using AVAudioPlayer for real audio output.
@Observable
final class PlaybackViewModel: NSObject, AVAudioPlayerDelegate {

    // MARK: - Playback State

    /// Current playback position in milliseconds.
    var currentPlaybackTimeMs: Double = 0

    /// Whether audio is currently playing.
    private(set) var isPlaying: Bool = false

    /// A user-facing reason why the selected recording cannot be played.
    private(set) var playbackError: String?

    /// Total session duration in milliseconds.
    var totalDurationMs: Double = 0
    
    // MARK: - Silence Skip State
    
    /// Whether auto-skip silence is enabled
    var isSilenceSkipEnabled: Bool = false
    
    /// Whether background silence analysis is active
    var isAnalyzingSilence: Bool = false
    
    /// Detected silent intervals
    var silentRanges: [SilenceRange] = []

    /// Amplitude levels extracted from the playback file on the live-recording scale.
    var waveformSamples: [Float] = []

    /// Whether playback waveform and silence analysis is active.
    var isAnalyzingAudio: Bool = false

    // MARK: - Audio Player Private State
    private var audioPlayer: AVAudioPlayer?
    private var activeAudioURL: URL?
    private var audioAnalysisTask: Task<Void, Never>?

    // MARK: - Computed Properties

    /// Formats the current playback position as `MM:SS`.
    var currentTimeFormatted: String {
        let totalSeconds = Int(currentPlaybackTimeMs / 1000)
        let minutes = totalSeconds / 60
        let seconds = totalSeconds % 60
        return String(format: "%02d:%02d", minutes, seconds)
    }

    /// Total duration formatted as `MM:SS`.
    var totalTimeFormatted: String {
        let totalSeconds = Int(totalDurationMs / 1000)
        let minutes = totalSeconds / 60
        let seconds = totalSeconds % 60
        return String(format: "%02d:%02d", minutes, seconds)
    }

    /// Playback progress as a normalized value (0.0 – 1.0).
    var progress: Double {
        guard totalDurationMs > 0 else { return 0 }
        return min(max(currentPlaybackTimeMs / totalDurationMs, 0), 1)
    }

    // MARK: - Private

    /// Tick timer that advances playback position at ~50ms intervals.
    private var playbackTimer: Timer?

    /// Tick interval in seconds (≈20 ticks/sec for smooth scrubber movement).
    private let tickInterval: TimeInterval = 0.05

    // MARK: - Init / Deinit

    override init() {
        super.init()
    }

    deinit {
        playbackTimer?.invalidate()
        audioAnalysisTask?.cancel()
        audioPlayer?.stop()
    }

    // MARK: - Player Preparation

    /// Prepares the audio player for the given local recording file.
    func preparePlayer(path: String?, durationMs: Double = 0) {
        stopPlaybackTimer()
        audioPlayer?.stop()
        audioPlayer = nil
        activeAudioURL = nil
        playbackError = nil

        guard let targetURL = AudioPathHelper.resolveURL(for: path) else {
            print("[PlaybackViewModel] No valid audio file found for path: \(path ?? "nil")")
            playbackError = "未找到本地录音文件，请先从云端同步或重新录制"
            if durationMs > 0 {
                self.totalDurationMs = durationMs
            }
            return
        }
        self.activeAudioURL = targetURL

        setupAudioSession()

        do {
            let player = try AVAudioPlayer(contentsOf: targetURL)
            player.delegate = self
            player.prepareToPlay()
            player.volume = 1.0
            self.audioPlayer = player
            // The decoded file is the authoritative clock for playback and waveform data.
            self.totalDurationMs = player.duration.isFinite && player.duration > 0
                ? player.duration * 1000.0
                : durationMs
        } catch {
            print("[PlaybackViewModel] Failed to initialize AVAudioPlayer: \(error.localizedDescription)")
            playbackError = "录音文件无法播放：\(error.localizedDescription)"
        }
    }

    private func setupAudioSession() {
        let session = AVAudioSession.sharedInstance()
        do {
            // `.playback` already routes through the speaker by default.
            // `.defaultToSpeaker` is valid only with `.playAndRecord`.
            try session.setCategory(.playback, mode: .default)
            try session.setActive(true)
        } catch {
            print("[PlaybackViewModel] Failed to setup AVAudioSession: \(error.localizedDescription)")
        }
    }

    // MARK: - Playback Controls

    /// Starts or resumes playback from the current position.
    func play() {
        guard !isPlaying else { return }
        guard let player = audioPlayer else {
            playbackError = "当前记录没有可播放的本地录音文件"
            return
        }

        setupAudioSession()

        player.currentTime = currentPlaybackTimeMs / 1000.0
        guard player.play() else {
            playbackError = "录音播放启动失败"
            return
        }

        isPlaying = true
        startPlaybackTimer()
    }

    /// Pauses playback at the current position.
    func pause() {
        guard isPlaying else { return }
        audioPlayer?.pause()
        isPlaying = false
        stopPlaybackTimer()
    }

    /// Toggles between playing and paused states.
    func togglePlayback() {
        if isPlaying {
            pause()
        } else {
            play()
        }
    }

    /// Seeks to a specific position in the timeline.
    /// - Parameter timeMs: The target position in milliseconds.
    func seekTo(timeMs: Double) {
        let target = min(max(timeMs, 0), totalDurationMs)
        currentPlaybackTimeMs = target
        if let player = audioPlayer {
            player.currentTime = target / 1000.0
        }
    }

    /// Convenience overload accepting Int64.
    func seekTo(timeMs: Int64) {
        seekTo(timeMs: Double(timeMs))
    }

    /// Seeks to a normalized progress value (0.0 – 1.0).
    /// - Parameter progress: Normalized position.
    func seekToProgress(_ progress: Double) {
        let clampedProgress = min(max(progress, 0), 1)
        seekTo(timeMs: clampedProgress * totalDurationMs)
    }

    /// Resets playback to the beginning.
    func reset() {
        pause()
        seekTo(timeMs: 0.0)
    }

    // MARK: - AVAudioPlayerDelegate

    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        isPlaying = false
        currentPlaybackTimeMs = totalDurationMs
        stopPlaybackTimer()
    }

    // MARK: - Private Timer & Silence Detection

    private func startPlaybackTimer() {
        playbackTimer?.invalidate()
        playbackTimer = Timer.scheduledTimer(
            withTimeInterval: tickInterval,
            repeats: true
        ) { [weak self] _ in
            guard let self else { return }
            self.tick()
        }
        if let timer = playbackTimer {
            RunLoop.main.add(timer, forMode: .common)
        }
    }

    private func stopPlaybackTimer() {
        playbackTimer?.invalidate()
        playbackTimer = nil
    }

    /// Runs silence detection asynchronously.
    func analyzeSilence(audioURL: URL?) {
        analyzeAudio(audioURL: audioURL)
    }

    /// Extracts the real waveform and silence ranges in one background pass.
    func analyzeAudio(audioURL: URL?) {
        audioAnalysisTask?.cancel()
        let targetPath = audioURL?.path ?? activeAudioURL?.path
        guard let url = AudioPathHelper.resolveURL(for: targetPath) else {
            waveformSamples = []
            silentRanges = []
            isAnalyzingSilence = false
            isAnalyzingAudio = false
            return
        }
        
        waveformSamples = []
        silentRanges = []
        isAnalyzingSilence = true
        isAnalyzingAudio = true
        audioAnalysisTask = Task { @MainActor [weak self] in
            let result = await SilenceDetector.analyze(url)
            guard !Task.isCancelled else { return }
            guard let self else { return }
            self.waveformSamples = result.waveformSamples
            self.silentRanges = result.silentRanges
            self.isAnalyzingSilence = false
            self.isAnalyzingAudio = false
        }
    }
    
    private func checkForSilenceAndSkip() {
        guard isSilenceSkipEnabled && !silentRanges.isEmpty else { return }
        
        let currentTimeSec = currentPlaybackTimeMs / 1000.0
        for range in silentRanges {
            if currentTimeSec >= range.start && currentTimeSec < range.end {
                let targetMs = range.end * 1000.0
                seekTo(timeMs: targetMs)
                print("[PlaybackViewModel] Auto-skipped silence from \(range.start)s to \(range.end)s")
                break
            }
        }
    }

    /// Advances playback by one tick interval, syncing with audioPlayer if active.
    private func tick() {
        guard let player = audioPlayer, player.isPlaying else {
            isPlaying = false
            stopPlaybackTimer()
            return
        }
        currentPlaybackTimeMs = player.currentTime * 1000.0
        
        checkForSilenceAndSkip()

        if currentPlaybackTimeMs >= totalDurationMs {
            currentPlaybackTimeMs = totalDurationMs
            pause()
        }
    }

    // MARK: - Event Queries

    func sortedEvents(from events: [CelestiaTimelineEvent]) -> [CelestiaTimelineEvent] {
        events.sorted { $0.relativeTimeMs < $1.relativeTimeMs }
    }

    func sortedEvents(from session: CelestiaSession) -> [CelestiaTimelineEvent] {
        sortedEvents(from: session.events)
    }

    func filteredEvents(
        from events: [CelestiaTimelineEvent],
        type: String?
    ) -> [CelestiaTimelineEvent] {
        let filtered: [CelestiaTimelineEvent]
        if let type {
            filtered = events.filter { $0.eventType == type }
        } else {
            filtered = events
        }
        return filtered.sorted { $0.relativeTimeMs < $1.relativeTimeMs }
    }

    func nearestEvent(
        from events: [CelestiaTimelineEvent],
        toleranceMs: Double = 500
    ) -> CelestiaTimelineEvent? {
        events
            .min(by: {
                abs(Double($0.relativeTimeMs) - currentPlaybackTimeMs)
                    < abs(Double($1.relativeTimeMs) - currentPlaybackTimeMs)
            })
            .flatMap { event in
                abs(Double(event.relativeTimeMs) - currentPlaybackTimeMs) <= toleranceMs
                    ? event
                    : nil
            }
    }

}
