//
//  InputType.swift
//  SwiftOpenAI
//
//  Created by James Rochabrun on 3/15/25.
//

import Foundation

// MARK: - InputType

/// Text, image, or file inputs to the model, used to generate a response.
///
/// Learn more:
///
/// [Text inputs and outputs](https://platform.openai.com/docs/guides/text)
/// [Image inputs](https://platform.openai.com/docs/guides/images)
/// [File inputs](https://platform.openai.com/docs/guides/pdf-files)
/// [Conversation state](https://platform.openai.com/docs/guides/conversation-state)
/// [Function calling](https://platform.openai.com/docs/guides/function-calling)
public enum InputType: Codable {
  /// A text input to the model, equivalent to a text input with the user role.
  case string(String)

  /// A list of one or many input items to the model, containing different content types.
  case array([InputItem])

  public init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()

    if let text = try? container.decode(String.self) {
      self = .string(text)
    } else if let array = try? container.decode([InputItem].self) {
      self = .array(array)
    } else {
      throw DecodingError.dataCorruptedError(
        in: container,
        debugDescription: "Input must be a string or an array of input items")
    }
  }

  public func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()
    switch self {
    case .string(let text):
      try container.encode(text)
    case .array(let items):
      try container.encode(items)
    }
  }
}

// MARK: - InputItem

/// An item representing part of the context for the response to be generated by the model.
/// Can contain text, images, and audio inputs, as well as previous assistant responses and tool call outputs.
public enum InputItem: Codable {
  /// Input message with role and content
  case message(InputMessage)

  /// Output message from the model (for conversation history)
  case outputMessage(OutputMessage)

  /// File search tool call
  case fileSearchToolCall(FileSearchToolCall)

  /// Function tool call
  case functionToolCall(FunctionToolCall)

  /// Function tool call output
  case functionToolCallOutput(FunctionToolCallOutput)

  /// Computer tool call output
  case computerToolCallOutput(ComputerToolCallOutput)

  /// Local shell call output
  case localShellCallOutput(LocalShellCallOutput)

  /// MCP approval response
  case mcpApprovalResponse(MCPApprovalResponse)

  public init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    let type = try container.decode(String.self, forKey: .type)

    switch type {
    case "message":
      self = try .message(InputMessage(from: decoder))
    case "file_search_call":
      self = try .fileSearchToolCall(FileSearchToolCall(from: decoder))
    case "function_call":
      self = try .functionToolCall(FunctionToolCall(from: decoder))
    case "function_call_output":
      self = try .functionToolCallOutput(FunctionToolCallOutput(from: decoder))
    case "computer_call_output":
      self = try .computerToolCallOutput(ComputerToolCallOutput(from: decoder))
    case "local_shell_call_output":
      self = try .localShellCallOutput(LocalShellCallOutput(from: decoder))
    case "mcp_approval_response":
      self = try .mcpApprovalResponse(MCPApprovalResponse(from: decoder))
    default:
      // Try to decode as message if type is not recognized
      self = try .message(InputMessage(from: decoder))
    }
  }

  public func encode(to encoder: Encoder) throws {
    switch self {
    case .message(let message):
      try message.encode(to: encoder)
    case .outputMessage(let message):
      try message.encode(to: encoder)
    case .fileSearchToolCall(let call):
      try call.encode(to: encoder)
    case .functionToolCall(let call):
      try call.encode(to: encoder)
    case .functionToolCallOutput(let output):
      try output.encode(to: encoder)
    case .computerToolCallOutput(let output):
      try output.encode(to: encoder)
    case .localShellCallOutput(let output):
      try output.encode(to: encoder)
    case .mcpApprovalResponse(let response):
      try response.encode(to: encoder)
    }
  }

  private enum CodingKeys: String, CodingKey {
    case type
    case role
  }
}

// MARK: - InputMessage

/// A message input to the model with a role indicating instruction following hierarchy
public struct InputMessage: Codable {
  public init(role: String, content: MessageContent, type: String? = "message", status: String? = nil, id: String? = nil) {
    self.role = role
    self.content = content
    self.type = type
    self.status = status
    self.id = id
  }

  /// The role of the message input (user, system, assistant, developer)
  public let role: String

  /// The content of the message
  public let content: MessageContent

  /// The type of the message input. Always "message"
  public let type: String?

  /// The status of item. Populated when items are returned via API
  public let status: String?

  /// The unique ID of the message (for assistant messages with response IDs)
  public let id: String?
}

// MARK: - MessageContent

/// Content for messages - can be string or array of content items
public enum MessageContent: Codable {
  /// Text input as string
  case text(String)

  /// Array of content items
  case array([ContentItem])

  public init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()

    if let text = try? container.decode(String.self) {
      self = .text(text)
    } else if let array = try? container.decode([ContentItem].self) {
      self = .array(array)
    } else {
      throw DecodingError.dataCorruptedError(
        in: container,
        debugDescription: "Content must be a string or an array of content items")
    }
  }

  public func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()
    switch self {
    case .text(let text):
      try container.encode(text)
    case .array(let items):
      try container.encode(items)
    }
  }
}

// MARK: - ContentItem

/// Content item types for messages
public enum ContentItem: Codable {
  /// Text content
  case text(TextContent)

  /// Image content
  case image(ImageContent)

  /// File content
  case file(FileContent)

  /// Audio content
  case audio(AudioContent)

  /// Output text (for assistant messages)
  case outputText(OutputTextContent)

  /// Refusal (for assistant messages)
  case refusal(RefusalContent)

  public init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    let type = try container.decode(String.self, forKey: .type)

    let singleValueContainer = try decoder.singleValueContainer()

    switch type {
    case "input_text":
      self = try .text(singleValueContainer.decode(TextContent.self))

    case "input_image":
      self = try .image(singleValueContainer.decode(ImageContent.self))

    case "input_file":
      self = try .file(singleValueContainer.decode(FileContent.self))

    case "input_audio":
      self = try .audio(singleValueContainer.decode(AudioContent.self))

    case "output_text":
      self = try .outputText(singleValueContainer.decode(OutputTextContent.self))

    case "refusal":
      self = try .refusal(singleValueContainer.decode(RefusalContent.self))

    default:
      throw DecodingError.dataCorruptedError(
        forKey: .type,
        in: container,
        debugDescription: "Unknown content type: \(type)")
    }
  }

  public func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()

    switch self {
    case .text(let text):
      try container.encode(text)
    case .image(let image):
      try container.encode(image)
    case .file(let file):
      try container.encode(file)
    case .audio(let audio):
      try container.encode(audio)
    case .outputText(let output):
      try container.encode(output)
    case .refusal(let refusal):
      try container.encode(refusal)
    }
  }

  private enum CodingKeys: String, CodingKey {
    case type
  }
}

// MARK: - TextContent

/// Text content structure
public struct TextContent: Codable {
  /// The text content
  public let text: String

  /// The type of content, always "input_text"
  public let type = "input_text"

  public init(text: String) {
    self.text = text
  }

  enum CodingKeys: String, CodingKey {
    case text
    case type
  }
}

// MARK: - ImageContent

/// Image content structure
public struct ImageContent: Codable {
  public init(detail: String? = "auto", fileId: String? = nil, imageUrl: String? = nil) {
    self.detail = detail
    self.fileId = fileId
    self.imageUrl = imageUrl
  }

  /// The type of content, always "input_image"
  public let type = "input_image"

  /// The detail level of the image. One of high, low, or auto. Defaults to auto when creating.
  public let detail: String?

  /// The ID of the file to be sent to the model
  public let fileId: String?

  /// The URL of the image to be sent to the model. A fully qualified URL or base64 encoded image in a data URL
  public let imageUrl: String?

  enum CodingKeys: String, CodingKey {
    case type
    case detail
    case fileId = "file_id"
    case imageUrl = "image_url"
  }
}

// MARK: - FileContent

/// File content structure
public struct FileContent: Codable {
  public init(fileData: String? = nil, fileId: String? = nil, fileUrl: String? = nil, filename: String? = nil) {
    self.fileData = fileData
    self.fileId = fileId
    self.fileUrl = fileUrl
    self.filename = filename
  }

  /// The type of content, always "input_file"
  public let type = "input_file"

  /// The content of the file to be sent to the model
  public let fileData: String?

  /// The ID of the file to be sent to the model
  public let fileId: String?

  /// The URL of the file to be sent to the model
  public let fileUrl: String?

  /// The name of the file to be sent to the model
  public let filename: String?

  enum CodingKeys: String, CodingKey {
    case type
    case fileData = "file_data"
    case fileId = "file_id"
    case fileUrl = "file_url"
    case filename
  }
}

// MARK: - AudioContent

/// Audio content structure
public struct AudioContent: Codable {
  /// The audio data
  public let data: String

  /// The format of the audio
  public let format: String

  /// The type of content, always "input_audio"
  public let type = "input_audio"

  public init(data: String, format: String) {
    self.data = data
    self.format = format
  }

  enum CodingKeys: String, CodingKey {
    case data
    case format
    case type
  }
}

// MARK: - OutputTextContent

/// A text output from the model
public struct OutputTextContent: Codable {
  public init(text: String, annotations: [Any]? = nil) {
    self.text = text
    self.annotations = annotations
  }

  /// Custom encoding/decoding to handle annotations
  public init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    text = try container.decode(String.self, forKey: .text)
    annotations = nil // Skip decoding annotations for now
  }

  /// The text content
  public let text: String

  /// Annotations in the text, if any
  public let annotations: [Any]? // For now, using Any. Can be made more specific later

  /// The type of the content. Always "output_text"
  public let type = "output_text"

  public func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(text, forKey: .text)
    try container.encode(type, forKey: .type)
    // Skip encoding annotations for now
  }

  enum CodingKeys: String, CodingKey {
    case text
    case annotations
    case type
  }
}

// MARK: - RefusalContent

/// A refusal from the model
public struct RefusalContent: Codable {
  /// The refusal explanation from the model
  public let refusal: String

  /// The type of the refusal. Always "refusal"
  public let type = "refusal"

  public init(refusal: String) {
    self.refusal = refusal
  }
}

// MARK: - OutputMessage

/// An output message from the model (used in conversation history)
public struct OutputMessage: Codable {
  public init(content: [ContentItem], id: String, role: String = "assistant", status: String? = nil, type: String = "message") {
    self.content = content
    self.id = id
    self.role = role
    self.status = status
    self.type = type
  }

  /// The content of the output message
  public let content: [ContentItem]

  /// The unique ID of the output message
  public let id: String

  /// The role of the output message. Always "assistant"
  public let role: String

  /// The status of the message. One of in_progress, completed, or incomplete. Populated when items are returned via API.
  public let status: String?

  /// The type of the output message. Always "message"
  public let type: String
}

// MARK: - FileSearchToolCall

/// File search tool call
public struct FileSearchToolCall: Codable {
  public let id: String
  public let queries: [String]
  public let status: String
  public let type = "file_search_call"
  public let results: [FileSearchResult]?

  public struct FileSearchResult: Codable {
    public let attributes: [String: String]?
    public let fileId: String?
    public let filename: String?
    public let score: Double?
    public let text: String?

    enum CodingKeys: String, CodingKey {
      case attributes
      case fileId = "file_id"
      case filename, score, text
    }
  }
}

// MARK: - FunctionToolCall

/// Function tool call
public struct FunctionToolCall: Codable {
  public init(arguments: String, callId: String, name: String, id: String? = nil, status: String? = nil) {
    self.arguments = arguments
    self.callId = callId
    self.name = name
    self.id = id
    self.status = status
  }

  public let arguments: String
  public let callId: String
  public let name: String
  public let type = "function_call"
  public let id: String?
  public let status: String?

  enum CodingKeys: String, CodingKey {
    case arguments
    case callId = "call_id"
    case name, type, id, status
  }
}

// MARK: - FunctionToolCallOutput

/// Function tool call output
public struct FunctionToolCallOutput: Codable {
  public let callId: String
  public let output: String
  public let type = "function_call_output"
  public let id: String?
  public let status: String?

  public init(callId: String, output: String, id: String? = nil, status: String? = nil) {
    self.callId = callId
    self.output = output
    self.id = id
    self.status = status
  }

  enum CodingKeys: String, CodingKey {
    case callId = "call_id"
    case output, type, id, status
  }
}

// MARK: - ComputerToolCallOutput

/// The output of a computer tool call
public struct ComputerToolCallOutput: Codable {
  public init(
    callId: String,
    id: String,
    output: ComputerScreenshot,
    acknowledgedSafetyChecks: [SafetyCheck]? = nil,
    status: String? = nil)
  {
    self.callId = callId
    self.id = id
    self.output = output
    self.acknowledgedSafetyChecks = acknowledgedSafetyChecks
    self.status = status
  }

  /// A computer screenshot
  public struct ComputerScreenshot: Codable {
    /// The type of the screenshot. Always "computer_screenshot"
    public let type: String

    /// The identifier of an uploaded file that contains the screenshot
    public let fileId: String?

    /// The URL of the screenshot image
    public let imageUrl: String?

    enum CodingKeys: String, CodingKey {
      case type
      case fileId = "file_id"
      case imageUrl = "image_url"
    }
  }

  /// Safety check structure
  public struct SafetyCheck: Codable {
    /// The type of the pending safety check
    public let code: String

    /// The ID of the pending safety check
    public let id: String

    /// Details about the pending safety check
    public let message: String
  }

  /// The ID of the computer tool call that produced the output
  public let callId: String

  /// The unique ID of the computer call tool output
  public let id: String

  /// A computer screenshot image used with the computer use tool
  public let output: ComputerScreenshot

  /// The type of the computer tool call output. Always "computer_call_output"
  public let type = "computer_call_output"

  /// The safety checks reported by the API that have been acknowledged by the developer
  public let acknowledgedSafetyChecks: [SafetyCheck]?

  /// The status of the message input. One of in_progress, completed, or incomplete
  public let status: String?

  enum CodingKeys: String, CodingKey {
    case callId = "call_id"
    case id
    case output
    case type
    case acknowledgedSafetyChecks = "acknowledged_safety_checks"
    case status
  }
}

// MARK: - LocalShellCallOutput

/// The output of a local shell tool call
public struct LocalShellCallOutput: Codable {
  public init(id: String, output: String, status: String? = nil) {
    self.id = id
    self.output = output
    self.status = status
  }

  /// The unique ID of the local shell tool call generated by the model
  public let id: String

  /// A JSON string of the output of the local shell tool call
  public let output: String

  /// The type of the local shell tool call output. Always "local_shell_call_output"
  public let type = "local_shell_call_output"

  /// The status of the item. One of in_progress, completed, or incomplete
  public let status: String?

  enum CodingKeys: String, CodingKey {
    case id, output, type, status
  }
}

// MARK: - MCPApprovalResponse

/// A response to an MCP approval request
public struct MCPApprovalResponse: Codable {
  public init(approvalRequestId: String, approve: Bool, id: String, reason: String? = nil) {
    self.approvalRequestId = approvalRequestId
    self.approve = approve
    self.id = id
    self.reason = reason
  }

  /// The ID of the approval request being answered
  public let approvalRequestId: String

  /// Whether the request was approved
  public let approve: Bool

  /// The unique ID of the approval response
  public let id: String

  /// The type of the item. Always "mcp_approval_response"
  public let type = "mcp_approval_response"

  /// Optional reason for the decision
  public let reason: String?

  enum CodingKeys: String, CodingKey {
    case approvalRequestId = "approval_request_id"
    case approve
    case id
    case type
    case reason
  }
}
