import AppKit
import Observation
import OpenClawKit

@MainActor
@Observable
final class TalkModeController {
    static let shared = TalkModeController()

    private let logger = Logger(subsystem: "ai.openclaw", category: "talk.controller")
    private static let transcriptLimit = 20

    private(set) var phase: TalkModePhase = .idle
    private(set) var isPaused: Bool = false
    private(set) var level: Double = 0
    private(set) var partialTranscript: String = ""
    private(set) var recentTranscripts: [String] = []

    /// Meters streamed PCM speech so the orb waveform follows the audible
    /// envelope instead of a synthetic pulse.
    @ObservationIgnored private lazy var playbackEnvelope = PCMPlaybackEnvelope { [weak self] level in
        self?.updateSpeakingLevel(level)
    }

    func setEnabled(_ enabled: Bool) async {
        self.logger.info("talk enabled=\(enabled)")
        if enabled {
            self.partialTranscript = ""
            self.recentTranscripts = []
            TalkOverlayController.shared.present()
            await VoiceWakeRuntime.shared.pauseForPushToTalk()
        } else {
            TalkOverlayController.shared.dismiss()
        }
        TalkSpeechInterruptMonitor.shared.setEnabled(enabled && AppStateStore.shared.talkShiftToStopEnabled)
        // Talk Mode and Push-to-Talk share the right Option key — disable PTT while Talk Mode is active.
        let pttEnabled = !enabled && AppStateStore.shared.voicePushToTalkEnabled
        VoicePushToTalkHotkey.shared.setEnabled(pttEnabled)
        await TalkModeRuntime.shared.setEnabled(enabled)
        // Resume voice wake listener *after* TalkMode audio is fully torn down.
        // Check swabbleEnabled (not voiceWakeTriggersTalkMode) so the paused wake listener
        // resumes even if the user toggled "Trigger Talk Mode" off during the session.
        if !enabled, AppStateStore.shared.swabbleEnabled {
            Task { await VoiceWakeRuntime.shared.refresh(state: AppStateStore.shared) }
        }
    }

    func updatePhase(_ phase: TalkModePhase) {
        let previousPhase = self.phase
        self.phase = phase
        if phase == .idle || phase == .thinking {
            self.updateLevel(0)
        }
        TalkOverlayController.shared.updatePhase(phase)

        // Play distinct system sounds for each phase transition.
        if phase != previousPhase {
            Self.playPhaseSound(phase, previousPhase: previousPhase)
        }

        let effectivePhase = self.isPaused ? "paused" : phase.rawValue
        Task {
            await GatewayConnection.shared.talkMode(
                enabled: AppStateStore.shared.talkEnabled,
                phase: effectivePhase)
        }
    }

    private static func playPhaseSound(_ phase: TalkModePhase, previousPhase: TalkModePhase) {
        guard AppStateStore.shared.talkPhaseSoundsEnabled else { return }
        let soundName: String? = switch phase {
        case .thinking:
            "Tink" // 생각 중: 짧고 가벼운 소리
        case .speaking:
            "Pop" // 대답 시작: 톡 소리
        case .listening:
            // 대답 중단(speaking→listening): 부드러운 종료음
            // 듣기 시작(thinking→listening 등): 잠수함 소리
            previousPhase == .speaking ? "Bottle" : "Submarine"
        case .idle:
            nil
        }
        if let soundName {
            NSSound(named: NSSound.Name(soundName))?.play()
        }
    }

    func updateLevel(_ level: Double) {
        let clamped = min(max(level, 0), 1)
        if clamped == 0 {
            self.level = 0
        } else {
            let response = clamped > self.level ? 0.45 : 0.18
            self.level += (clamped - self.level) * response
        }
        TalkOverlayController.shared.updateLevel(self.level)
    }

    /// Playback level published while agent speech plays; nil (path without
    /// metering, or playback ended) settles the wave back to its floor.
    func updateSpeakingLevel(_ level: Double?) {
        guard self.phase == .speaking else { return }
        self.updateLevel(level ?? 0)
    }

    func updatePartialTranscript(_ transcript: String) {
        self.partialTranscript = transcript.trimmingCharacters(in: .whitespacesAndNewlines)
    }

    func commitTranscript(_ transcript: String) {
        let trimmed = transcript.trimmingCharacters(in: .whitespacesAndNewlines)
        self.partialTranscript = ""
        guard !trimmed.isEmpty else { return }
        self.recentTranscripts.append(trimmed)
        if self.recentTranscripts.count > Self.transcriptLimit {
            self.recentTranscripts.removeFirst(self.recentTranscripts.count - Self.transcriptLimit)
        }
    }

    /// Passes streamed PCM speech through to the player while feeding the
    /// playback envelope; call `endSpeechMetering` once playback returns.
    func meteredSpeechStream(
        _ stream: AsyncThrowingStream<Data, Error>,
        sampleRate: Double) -> AsyncThrowingStream<Data, Error>
    {
        self.playbackEnvelope.metering(stream, sampleRate: sampleRate)
    }

    func endSpeechMetering() {
        self.playbackEnvelope.cancel()
    }

    func setPaused(_ paused: Bool) {
        guard self.isPaused != paused else { return }
        self.logger.info("talk paused=\(paused)")
        self.isPaused = paused
        TalkOverlayController.shared.updatePaused(paused)
        let effectivePhase = paused ? "paused" : self.phase.rawValue
        Task {
            await GatewayConnection.shared.talkMode(
                enabled: AppStateStore.shared.talkEnabled,
                phase: effectivePhase)
        }
        Task { await TalkModeRuntime.shared.setPaused(paused) }
    }

    func togglePaused() {
        self.setPaused(!self.isPaused)
    }

    func stopSpeaking(reason: TalkStopReason = .userTap) {
        Task { await TalkModeRuntime.shared.stopSpeaking(reason: reason) }
    }

    func exitTalkMode() {
        Task { await AppStateStore.shared.setTalkEnabled(false) }
    }
}

enum TalkStopReason {
    case userTap
    case speech
    case manual
}
