//
// OpenRocky — Voice-first AI Agent
// https://github.com/openrocky
//
// Developed by everettjf with the assistance of Claude Code and Codex.
// Date: 2026-04-25
// Copyright (c) 2026 everettjf. All rights reserved.
//

import Foundation

/// Advanced knobs for an OpenAI Realtime voice session.
///
/// These are persisted on a per-`OpenRockyRealtimeProviderInstance` basis so the
/// realtime API key, model, voice, and tuning all live together. Defaults are
/// chosen for `gpt-realtime-2` on the GA session schema — semantic VAD, audio
/// output (transcript events come over the data channel automatically), and
/// `low` reasoning effort (the OpenAI-recommended starting point that keeps
/// turn latency low for typical back-and-forth).
struct OpenRockyRealtimeAdvancedSettings: Codable, Equatable, Sendable {
    // MARK: - Models
    /// Realtime model used by the voice session itself.
    var realtimeModel: String
    /// Speech-to-text model used for input transcription.
    var transcriptionModel: TranscriptionModel
    /// Optional ISO-639-1 language hint for the transcriber. When nil, OpenAI auto-detects.
    /// Setting this for non-English users (`zh`, `ja`, `de`, ...) noticeably improves accuracy and latency.
    var inputLanguage: String?

    // MARK: - Reasoning
    /// How much the model "thinks" before responding. `low` keeps voice latency
    /// low for casual exchanges; bump to `medium`/`high` for harder tool-use
    /// chains or stricter instruction following. `xhigh` is best left for
    /// non-voice batch tasks — it can stall the conversation.
    var reasoningEffort: ReasoningEffort

    // MARK: - Turn detection
    var turnDetection: TurnDetectionConfig

    // MARK: - Generation
    /// Cap on assistant response tokens per turn (excluding tool calls).
    var maxOutputTokens: Int
    /// Audio playback speed for the assistant. 1.0 = natural pace.
    var speed: Double
    /// When true, assistant may respond text-only (no audio). Saves tokens for visual replies but
    /// breaks voice continuity — only enable if the user explicitly asks for muted output.
    var allowTextOnly: Bool

    // MARK: - Barge-in
    /// RMS threshold for detecting that the user started speaking while the assistant is playing
    /// audio. Lower = more sensitive (more false-positives), higher = more conservative.
    /// Only consulted when the "Voice Interruption" toggle is on in Preferences.
    var bargeInRMSThreshold: Double

    // MARK: - Defaults

    nonisolated static let `default` = OpenRockyRealtimeAdvancedSettings(
        realtimeModel: "gpt-realtime-2",
        transcriptionModel: .gpt4oMiniTranscribe,
        inputLanguage: nil,
        reasoningEffort: .low,
        turnDetection: .semanticVAD(eagerness: .medium),
        maxOutputTokens: 1024,
        speed: 1.0,
        allowTextOnly: false,
        bargeInRMSThreshold: 3500
    )

    nonisolated init(
        realtimeModel: String,
        transcriptionModel: TranscriptionModel,
        inputLanguage: String?,
        reasoningEffort: ReasoningEffort,
        turnDetection: TurnDetectionConfig,
        maxOutputTokens: Int,
        speed: Double,
        allowTextOnly: Bool,
        bargeInRMSThreshold: Double
    ) {
        self.realtimeModel = realtimeModel
        self.transcriptionModel = transcriptionModel
        self.inputLanguage = inputLanguage
        self.reasoningEffort = reasoningEffort
        self.turnDetection = turnDetection
        self.maxOutputTokens = maxOutputTokens
        self.speed = speed
        self.allowTextOnly = allowTextOnly
        self.bargeInRMSThreshold = bargeInRMSThreshold
    }

    // Custom decoding tolerates legacy persisted shapes:
    // - pre-`reasoningEffort` blobs fall back to `.low`
    // - the now-removed `temperature` key (GA dropped it) is silently ignored
    nonisolated init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        self.realtimeModel = try c.decode(String.self, forKey: .realtimeModel)
        self.transcriptionModel = try c.decode(TranscriptionModel.self, forKey: .transcriptionModel)
        self.inputLanguage = try c.decodeIfPresent(String.self, forKey: .inputLanguage)
        self.reasoningEffort = try c.decodeIfPresent(ReasoningEffort.self, forKey: .reasoningEffort) ?? .low
        self.turnDetection = try c.decode(TurnDetectionConfig.self, forKey: .turnDetection)
        self.maxOutputTokens = try c.decode(Int.self, forKey: .maxOutputTokens)
        self.speed = try c.decode(Double.self, forKey: .speed)
        self.allowTextOnly = try c.decode(Bool.self, forKey: .allowTextOnly)
        self.bargeInRMSThreshold = try c.decode(Double.self, forKey: .bargeInRMSThreshold)
    }

    private enum CodingKeys: String, CodingKey {
        case realtimeModel
        case transcriptionModel
        case inputLanguage
        case reasoningEffort
        case turnDetection
        case maxOutputTokens
        case speed
        case allowTextOnly
        case bargeInRMSThreshold
    }
}

// MARK: - Reasoning effort

extension OpenRockyRealtimeAdvancedSettings {
    enum ReasoningEffort: String, Codable, CaseIterable, Identifiable, Sendable {
        case minimal
        case low
        case medium
        case high
        case xhigh

        var id: String { rawValue }

        var displayName: String {
            switch self {
            case .minimal: "Minimal"
            case .low: "Low (recommended)"
            case .medium: "Medium"
            case .high: "High"
            case .xhigh: "Extra High"
            }
        }

        var summary: String {
            switch self {
            case .minimal: "Snappiest. Skip reasoning when the model is barely thinking anyway."
            case .low: "OpenAI's default. Best latency / quality balance for everyday voice."
            case .medium: "More deliberate. Helps with multi-step tool chains."
            case .high: "Strong reasoning. Adds noticeable latency on each turn."
            case .xhigh: "Maximum thinking. Voice will feel sluggish — use only when needed."
            }
        }
    }
}

// MARK: - Transcription model

extension OpenRockyRealtimeAdvancedSettings {
    enum TranscriptionModel: String, Codable, CaseIterable, Identifiable, Sendable {
        case whisper1 = "whisper-1"
        case gpt4oTranscribe = "gpt-4o-transcribe"
        case gpt4oMiniTranscribe = "gpt-4o-mini-transcribe"

        var id: String { rawValue }

        var displayName: String {
            switch self {
            case .whisper1: "Whisper-1"
            case .gpt4oTranscribe: "GPT-4o Transcribe"
            case .gpt4oMiniTranscribe: "GPT-4o Mini Transcribe"
            }
        }

        var summary: String {
            switch self {
            case .whisper1: "Classic Whisper. Slower, broad language coverage."
            case .gpt4oTranscribe: "Highest quality. Best for noisy or accented speech."
            case .gpt4oMiniTranscribe: "Fast & cheap. Recommended default."
            }
        }
    }
}

// MARK: - Turn detection

extension OpenRockyRealtimeAdvancedSettings {
    /// Models how the server decides the user has finished a turn.
    /// `semanticVAD` understands phrase-level intent (recommended for current realtime models);
    /// `serverVAD` is energy/silence-based (compatible with all models, more tunable).
    enum TurnDetectionConfig: Codable, Equatable, Sendable {
        case semanticVAD(eagerness: SemanticVADEagerness)
        case serverVAD(prefixPaddingMs: Int, silenceDurationMs: Int, threshold: Double)

        enum Mode: String, CaseIterable, Identifiable, Sendable {
            case semantic
            case server
            var id: String { rawValue }
            var displayName: String {
                switch self {
                case .semantic: "Semantic VAD"
                case .server: "Server VAD"
                }
            }
            var summary: String {
                switch self {
                case .semantic: "Phrase-aware. Best for natural turn-taking."
                case .server: "Energy-based. Tune prefix/silence/threshold to taste."
                }
            }
        }

        nonisolated var mode: Mode {
            switch self {
            case .semanticVAD: .semantic
            case .serverVAD: .server
            }
        }
    }

    enum SemanticVADEagerness: String, Codable, CaseIterable, Identifiable, Sendable {
        case low
        case medium
        case high

        var id: String { rawValue }
        var displayName: String {
            switch self {
            case .low: "Low (waits longer)"
            case .medium: "Medium"
            case .high: "High (responds faster)"
            }
        }
    }
}
