import SwiftUI
import SwiftData
import Observation

// MARK: - RecordingViewModel

/// Manages recording state and bridges the ObservableObject-based AudioRecorder
/// into the iOS 17 Observation framework via Timer-based polling.
@Observable
final class RecordingViewModel {

    // MARK: - Published State

    private(set) var isRecording: Bool = false
    private(set) var initialDuration: TimeInterval = 0
    private(set) var elapsedTime: TimeInterval = 0
    private(set) var currentAmplitude: Float = 0
    private(set) var waveformSamples: [Float] = []
    private(set) var isPaused: Bool = false
    private(set) var sourceDisplayName: String
    private(set) var statusMessage: String = "正在准备"
    private(set) var errorMessage: String?

    // MARK: - Computed Properties

    /// Formats elapsed time as `HH:MM:SS`.
    var elapsedTimeFormatted: String {
        let totalSeconds = Int(elapsedTime)
        let hours = totalSeconds / 3600
        let minutes = (totalSeconds % 3600) / 60
        let seconds = totalSeconds % 60
        return String(format: "%02d:%02d:%02d", hours, minutes, seconds)
    }

    /// Local URL where the audio file is stored.
    var outputFileURL: URL? {
        recorder.outputFileURL
    }

    // MARK: - Private

    /// Concrete recorder instance conforming to AudioRecorderProtocol.
    private let recorder: any AudioRecorderProtocol

    /// Timer that syncs recorder state → ViewModel state at ~60 Hz.
    private var syncTimer: Timer?

    // MARK: - Init

    init() {
        self.recorder = RealAudioRecorder()
        self.sourceDisplayName = "iPhone 麦克风"
    }

    init(source: RecordingSourceChoice) {
        switch source {
        case .iPhone:
            self.recorder = RealAudioRecorder()
        case .spark(let deviceID, let displayName):
            self.recorder = SparkAudioRecorder(deviceID: deviceID, displayName: displayName)
        }
        self.sourceDisplayName = source.displayName
    }

    init(recorder: any AudioRecorderProtocol) {
        self.recorder = recorder
        self.sourceDisplayName = recorder.sourceDisplayName
    }

    deinit {
        syncTimer?.invalidate()
    }

    // MARK: - Recording Controls

    /// Starts recording and begins polling the recorder for state updates.
    /// - Parameter initialDuration: Prior recorded duration in seconds when continuing a session.
    func startRecording(initialDuration: TimeInterval = 0) {
        self.initialDuration = initialDuration
        self.elapsedTime = initialDuration
        recorder.startRecording()
        syncState()
        startSyncTimer()
    }

    /// Stops recording and cleans up the sync timer.
    func stopRecording() {
        recorder.stopRecording()
        isRecording = false
        stopSyncTimer()
        syncState() // Final sync
    }

    /// Stops the active source and reports whether the source confirmed that it
    /// is safe to finalize the session. Spark uses this to await PQ_DEV&STO.
    func stopRecording(completion: @escaping (Result<URL?, Error>) -> Void) {
        recorder.stopRecording { [weak self] result in
            guard let self else { return }
            self.isRecording = false
            self.stopSyncTimer()
            self.syncState()
            completion(result)
        }
    }

    /// Pauses the current recording session.
    func pauseRecording() {
        recorder.pauseRecording()
        syncState()
    }

    /// Resumes a paused recording session.
    func resumeRecording() {
        recorder.resumeRecording()
        syncState()
    }

    // MARK: - Timeline Event Creation

    /// Creates a PHOTO timeline event attached to the given session.
    /// - Parameter session: The active `CelestiaSession` to attach the event to.
    /// - Returns: The newly created `CelestiaTimelineEvent`.
    @discardableResult
    func addPhotoEvent(to session: CelestiaSession, localFilePath: String? = nil) -> CelestiaTimelineEvent {
        let relativeMs = Int64(elapsedTime * 1000)
        let event = CelestiaTimelineEvent(relativeTimeMs: relativeMs, eventType: "PHOTO")
        event.localFilePath = localFilePath
        session.events.append(event)
        session.isSynced = false
        session.syncState = .pending
        return event
    }

    /// Creates a NOTE timeline event attached to the given session.
    /// - Parameters:
    ///   - session: The active `CelestiaSession` to attach the event to.
    ///   - text: The note text content.
    /// - Returns: The newly created `CelestiaTimelineEvent`.
    @discardableResult
    func addNoteEvent(to session: CelestiaSession, text: String) -> CelestiaTimelineEvent {
        let relativeMs = Int64(elapsedTime * 1000)
        let event = CelestiaTimelineEvent(relativeTimeMs: relativeMs, eventType: "NOTE")
        event.textContent = text
        session.events.append(event)
        session.isSynced = false
        session.syncState = .pending
        return event
    }

    /// Creates a MARKER timeline event attached to the given session.
    /// - Parameters:
    ///   - session: The active `CelestiaSession` to attach the event to.
    ///   - text: The marker label text.
    /// - Returns: The newly created `CelestiaTimelineEvent`.
    @discardableResult
    func addMarkerEvent(to session: CelestiaSession, text: String) -> CelestiaTimelineEvent {
        let relativeMs = Int64(elapsedTime * 1000)
        let event = CelestiaTimelineEvent(relativeTimeMs: relativeMs, eventType: "MARKER")
        event.textContent = text
        session.events.append(event)
        session.isSynced = false
        session.syncState = .pending
        return event
    }

    // MARK: - Private Sync Timer

    private func startSyncTimer() {
        syncTimer?.invalidate()
        // ~60 Hz polling to bridge ObservableObject → @Observable
        syncTimer = Timer.scheduledTimer(withTimeInterval: 1.0 / 60.0, repeats: true) { [weak self] _ in
            self?.syncState()
        }
        // Ensure timer fires during UI tracking (scrolling, etc.)
        if let timer = syncTimer {
            RunLoop.main.add(timer, forMode: .common)
        }
    }

    private func stopSyncTimer() {
        syncTimer?.invalidate()
        syncTimer = nil
    }

    /// Pulls current values from the ObservableObject recorder into @Observable properties.
    private func syncState() {
        isRecording = recorder.isRecording
        elapsedTime = initialDuration + recorder.elapsedTime
        currentAmplitude = recorder.currentAmplitude
        waveformSamples = recorder.waveformSamples
        isPaused = recorder.isPaused
        sourceDisplayName = recorder.sourceDisplayName
        statusMessage = recorder.statusMessage
        errorMessage = recorder.errorMessage
    }
}
