// MARK: - OpenAIRealtimeSessionConfiguration

//
//  OpenAIRealtimeSessionConfiguration.swift
//  SwiftOpenAI
//
//  Created from AIProxySwift
//  Original: https://github.com/lzell/AIProxySwift
//
//  Rewritten 2026-05-08 for the OpenAI Realtime GA schema. The pre-GA "flat"
//  fields (`input_audio_format`, `output_audio_format`, `modalities`,
//  `voice`, `speed`, `turn_detection`, `input_audio_transcription`,
//  `max_response_output_tokens`) are gone — everything audio-related lives
//  under `audio.input` / `audio.output`, and `modalities` was renamed to
//  `output_modalities` (mutually exclusive: `["audio"]` OR `["text"]`).
//

/// Realtime session configuration (GA shape).
///
/// https://platform.openai.com/docs/api-reference/realtime-client-events/session/update
public struct OpenAIRealtimeSessionConfiguration: Encodable, Sendable {
  public init(
    audio: Audio? = nil,
    instructions: String? = nil,
    maxOutputTokens: MaxOutputTokens? = nil,
    outputModalities: [OutputModality]? = nil,
    reasoning: Reasoning? = nil,
    tools: [RealtimeTool]? = nil,
    toolChoice: ToolChoice? = nil)
  {
    self.audio = audio
    self.instructions = instructions
    self.maxOutputTokens = maxOutputTokens
    self.outputModalities = outputModalities
    self.reasoning = reasoning
    self.tools = tools
    self.toolChoice = toolChoice
  }

  /// Discriminator the realtime backend uses to tell speech-to-speech sessions
  /// apart from transcription-only sessions. Hard-coded to `"realtime"`.
  public let type = "realtime"

  /// All audio I/O — input format, transcription, turn detection on the input
  /// side; output format, voice, speed on the output side.
  public let audio: Audio?

  /// System prompt prepended to every model call.
  public let instructions: String?

  /// Cap on assistant response tokens per turn (excluding tool calls).
  /// Provide an integer or `.infinite`.
  public let maxOutputTokens: MaxOutputTokens?

  /// Modality for the assistant's *output*. GA only accepts a single-element
  /// array: `["audio"]` (default — voice with built-in transcript events) or
  /// `["text"]` (text-only, no TTS).
  public let outputModalities: [OutputModality]?

  /// Reasoning effort for reasoning-capable realtime models (e.g. `gpt-realtime-2`).
  /// Lower effort = lower latency; higher effort = better tool-use and
  /// complex-instruction following. OpenAI recommends starting at `low`.
  /// (GA dropped the legacy `temperature` knob — the model's architecture
  /// handles randomness internally; reasoning effort is the new dial.)
  public let reasoning: Reasoning?

  /// Tools (functions and MCP servers) available to the model.
  public let tools: [RealtimeTool]?

  /// How the model chooses tools.
  public let toolChoice: ToolChoice?

  private enum CodingKeys: String, CodingKey {
    case type
    case audio
    case instructions
    case maxOutputTokens = "max_output_tokens"
    case outputModalities = "output_modalities"
    case reasoning
    case tools
    case toolChoice = "tool_choice"
  }
}

// MARK: OpenAIRealtimeSessionConfiguration.Audio

extension OpenAIRealtimeSessionConfiguration {
  public struct Audio: Encodable, Sendable {
    public let input: Input?
    public let output: Output?

    public init(input: Input? = nil, output: Output? = nil) {
      self.input = input
      self.output = output
    }
  }
}

// MARK: OpenAIRealtimeSessionConfiguration.Audio.Input / Output

extension OpenAIRealtimeSessionConfiguration.Audio {
  public struct Input: Encodable, Sendable {
    /// Input audio container. `pcm16` at 24 kHz is the realtime-models default.
    public let format: Format?
    /// Optional input transcription. When set, transcript-deltas of the user's
    /// speech come back over the data channel.
    public let transcription: Transcription?
    /// How the server decides the user has finished speaking.
    public let turnDetection: TurnDetection?

    public init(
      format: Format? = nil,
      transcription: Transcription? = nil,
      turnDetection: TurnDetection? = nil)
    {
      self.format = format
      self.transcription = transcription
      self.turnDetection = turnDetection
    }

    private enum CodingKeys: String, CodingKey {
      case format
      case transcription
      case turnDetection = "turn_detection"
    }
  }

  public struct Output: Encodable, Sendable {
    /// Output audio container. Defaults to PCM16 24 kHz when omitted.
    public let format: Format?
    /// Voice ID (e.g. `alloy`, `marin`, `cedar`). Cannot be changed once the
    /// model has emitted audio at least once.
    public let voice: String?
    /// Playback speed for synthesized audio. 0.25–4.0; default 1.0.
    public let speed: Float?

    public init(
      format: Format? = nil,
      voice: String? = nil,
      speed: Float? = nil)
    {
      self.format = format
      self.voice = voice
      self.speed = speed
    }
  }
}

// MARK: OpenAIRealtimeSessionConfiguration.Audio.Format

extension OpenAIRealtimeSessionConfiguration.Audio {
  /// Audio container — encoded as `{"type": "audio/pcm", "rate": 24000}` etc.
  public struct Format: Encodable, Sendable {
    public enum MIME: String, Encodable, Sendable {
      case pcm = "audio/pcm"
      case pcmu = "audio/pcmu"
      case pcma = "audio/pcma"
    }

    public let type: MIME
    /// Sample rate in Hz. Optional — omitted lets the server pick the model
    /// default (24000 for `audio/pcm`, 8000 for `pcmu`/`pcma`).
    public let rate: Int?

    public init(type: MIME, rate: Int? = nil) {
      self.type = type
      self.rate = rate
    }

    /// Convenience: PCM16 at 24 kHz, the realtime-models default.
    public static let pcm24k = Format(type: .pcm, rate: 24_000)
  }
}

// MARK: OpenAIRealtimeSessionConfiguration.Audio.Input.Transcription

extension OpenAIRealtimeSessionConfiguration.Audio.Input {
  public struct Transcription: Encodable, Sendable {
    /// e.g. `gpt-4o-mini-transcribe`, `gpt-4o-transcribe`, `whisper-1`.
    public let model: String
    /// ISO-639-1 hint (`zh`, `en`, `ja`, ...). Improves accuracy / latency.
    public let language: String?

    public init(model: String, language: String? = nil) {
      self.model = model
      self.language = language
    }
  }
}

// MARK: OpenAIRealtimeSessionConfiguration.Audio.Input.TurnDetection

extension OpenAIRealtimeSessionConfiguration.Audio.Input {
  public struct TurnDetection: Encodable, Sendable {
    public let type: DetectionType

    public init(type: DetectionType) { self.type = type }

    public func encode(to encoder: any Encoder) throws {
      var container = encoder.container(keyedBy: CodingKeys.self)
      switch type {
      case .serverVAD(let prefixPaddingMs, let silenceDurationMs, let threshold):
        try container.encode("server_vad", forKey: .type)
        try container.encode(prefixPaddingMs, forKey: .prefixPaddingMs)
        try container.encode(silenceDurationMs, forKey: .silenceDurationMs)
        try container.encode(threshold, forKey: .threshold)
      case .semanticVAD(let eagerness):
        try container.encode("semantic_vad", forKey: .type)
        try container.encode(String(describing: eagerness), forKey: .eagerness)
      }
    }

    private enum CodingKeys: String, CodingKey {
      case type
      case prefixPaddingMs = "prefix_padding_ms"
      case silenceDurationMs = "silence_duration_ms"
      case threshold
      case eagerness
    }

    public enum DetectionType: Sendable {
      /// - prefixPaddingMs: audio to include before detected speech (default 300)
      /// - silenceDurationMs: silence required to end the turn (default 500)
      /// - threshold: VAD activation level 0.0–1.0 (default 0.5)
      case serverVAD(prefixPaddingMs: Int, silenceDurationMs: Int, threshold: Double)
      /// Phrase-aware VAD; `eagerness` controls how readily the model takes a turn.
      case semanticVAD(eagerness: Eagerness)

      public enum Eagerness: String, Encodable, Sendable {
        case low
        case medium
        case high
      }
    }
  }
}

// MARK: OpenAIRealtimeSessionConfiguration.MaxOutputTokens

extension OpenAIRealtimeSessionConfiguration {
  public enum MaxOutputTokens: Encodable, Sendable {
    case int(Int)
    case infinite

    public func encode(to encoder: Encoder) throws {
      var container = encoder.singleValueContainer()
      switch self {
      case .int(let value):
        try container.encode(value)
      case .infinite:
        try container.encode("inf")
      }
    }
  }
}

// MARK: OpenAIRealtimeSessionConfiguration.OutputModality

extension OpenAIRealtimeSessionConfiguration {
  /// GA only accepts a single-element output_modalities array. Use `.audio`
  /// (voice + transcript events) or `.text` (text-only). Mixing both is no
  /// longer supported.
  public enum OutputModality: String, Encodable, Sendable {
    case audio
    case text
  }
}

// MARK: OpenAIRealtimeSessionConfiguration.Reasoning

extension OpenAIRealtimeSessionConfiguration {
  public struct Reasoning: Encodable, Sendable {
    public enum Effort: String, Encodable, Sendable {
      case minimal
      case low
      case medium
      case high
      case xhigh
    }

    public let effort: Effort

    public init(effort: Effort) {
      self.effort = effort
    }
  }
}

// MARK: OpenAIRealtimeSessionConfiguration.ToolChoice

extension OpenAIRealtimeSessionConfiguration {
  public enum ToolChoice: Encodable, Sendable {
    case none
    case auto
    case required
    case specific(functionName: String)

    public func encode(to encoder: any Encoder) throws {
      switch self {
      case .none:
        var c = encoder.singleValueContainer()
        try c.encode("none")
      case .auto:
        var c = encoder.singleValueContainer()
        try c.encode("auto")
      case .required:
        var c = encoder.singleValueContainer()
        try c.encode("required")
      case .specific(let functionName):
        var c = encoder.container(keyedBy: RootKey.self)
        try c.encode("function", forKey: .type)
        try c.encode(functionName, forKey: .name)
      }
    }

    private enum RootKey: CodingKey {
      case type
      case name
    }
  }
}

// MARK: OpenAIRealtimeSessionConfiguration.FunctionTool / RealtimeTool

extension OpenAIRealtimeSessionConfiguration {
  public struct FunctionTool: Encodable, Sendable {
    public let description: String
    public let name: String
    public let parameters: [String: OpenAIJSONValue]
    public let type = "function"

    public init(name: String, description: String, parameters: [String: OpenAIJSONValue]) {
      self.name = name
      self.description = description
      self.parameters = parameters
    }
  }

  /// A tool entry on the session — either a function the client implements,
  /// or a remote MCP server the realtime backend can connect to directly.
  public enum RealtimeTool: Encodable, Sendable {
    case function(FunctionTool)
    case mcp(Tool.MCPTool)

    public func encode(to encoder: Encoder) throws {
      switch self {
      case .function(let tool):
        try tool.encode(to: encoder)
      case .mcp(let mcpTool):
        try mcpTool.encode(to: encoder)
      }
    }
  }
}
