///|
fn model_from_generated(model : @gen.Model) -> Model {
  { id: model.id, created: Some(model.created), owned_by: model.owned_by, }
}

///|
fn embedding_response_from_generated(
  response : @gen.CreateEmbeddingResponse,
) -> EmbeddingResponse {
  {
    data: response.data.map(item => {
      index: item.index,
      embedding: item.embedding,
    }),
    model: response.model,
    prompt_tokens: response.usage.prompt_tokens,
    total_tokens: response.usage.total_tokens,
  }
}

///|
async fn[T, M : @client.ResponseMode] OpenAI::send_with(
  self : OpenAI,
  request : @http.Request,
  reader : @client.ResponsePlan[Result[T, @client.ResponseFailure], M],
  bucket : String,
) -> Result[T, OpenAIFailure] {
  try {
    match
      @client.RequestPlan::new(request, reader)
      .attribute(@client.rate_limit_bucket, bucket)
      .send(self.client).body {
      Ok(value) => Ok(value)
      Err(error) => Err(Response(error))
    }
  } catch {
    DecodeError(error) => Err(Response(error))
    error => raise error
  }
}

///|
/// Creates and buffers one Chat Completions response.
pub async fn OpenAI::chat_completion(
  self : OpenAI,
  request : ChatRequest,
) -> Result[ChatCompletion, OpenAIFailure] {
  try {
    let (generated, body) = chat_parts(request, false)
    self.send_with(
      @gen.create_chat_completion_request(generated).body_json(body),
      decode_reader((bytes, head) => decode_chat_completion(head, bytes)),
      "chat",
    )
  } catch {
    ValidationError(message~) => Err(Validation(message))
    error => raise error
  }
}

///|
fn decode_chat_stream_data(
  head : @http.ResponseHead,
  data : String,
) -> Array[ChatEvent] raise DecodeError {
  let body = @utf8.encode(data)
  let raw = parse_json_body(body) catch {
    error => raise decode_error(head, message=error.to_string(), body~)
  }
  decode_chat_chunk(raw) catch {
    error => raise decode_error(head, message=error.to_string(), body~)
  }
}

///|
/// Dispatches Chat Completions events in wire order, including final usage.
/// EOF, [DONE], decoding failure and callback failure close the stream.
pub async fn OpenAI::stream_chat_completion(
  self : OpenAI,
  request : ChatRequest,
  f : async (ChatEvent) -> Unit,
) -> Result[Unit, OpenAIFailure] {
  try {
    let (generated, body) = chat_parts(request, true)
    self.send_with(
      @gen.create_chat_completion_request(generated)
      .header("accept", "text/event-stream")
      .body_json(body),
      @client.from_head(head => {
        @client.as_sse(
          event => {
            for decoded in decode_chat_stream_data(head, event.data) {
              f(decoded)
            }
          },
          stop=event => event.data == "[DONE]",
        )
      }),
      "chat",
    )
  } catch {
    ValidationError(message~) => Err(Validation(message))
    error => raise error
  }
}

///|
/// Lists the models visible to the configured account.
pub async fn OpenAI::list_models(
  self : OpenAI,
) -> Result[Array[Model], OpenAIFailure] {
  self.send_with(
    @gen.list_models_request(),
    decode_reader((bytes, head) => {
      take_decode(@gen.list_models_decode(head, bytes)).data.map(
        model_from_generated,
      )
    }),
    "models",
  ) catch {
    ValidationError(message~) => Err(Validation(message))
    error => raise error
  }
}

///|
/// Retrieves one model by identifier.
pub async fn OpenAI::retrieve_model(
  self : OpenAI,
  model : String,
) -> Result[Model, OpenAIFailure] {
  self.send_with(
    @gen.retrieve_model_request(model),
    decode_reader((bytes, head) => {
      model_from_generated(take_decode(@gen.retrieve_model_decode(head, bytes)))
    }),
    "models",
  ) catch {
    ValidationError(message~) => Err(Validation(message))
    error => raise error
  }
}

///|
/// Creates embeddings for every input string in the request.
pub async fn OpenAI::create_embeddings(
  self : OpenAI,
  request : EmbeddingRequest,
) -> Result[EmbeddingResponse, OpenAIFailure] {
  try {
    let body = @gen.CreateEmbeddingRequest::new(
      model=request.model,
      input=@gen.TextArray(request.input.copy()),
      dimensions?=request.dimensions,
      user?=request.user,
    )
    self.send_with(
      @gen.create_embedding_request(body),
      decode_reader((bytes, head) => {
        embedding_response_from_generated(
          take_decode(@gen.create_embedding_decode(head, bytes)),
        )
      }),
      "embeddings",
    )
  } catch {
    ValidationError(message~) => Err(Validation(message))
    error => raise error
  }
}

///|
/// Creates and buffers one model response.
pub async fn OpenAI::create_response(
  self : OpenAI,
  request : ResponseRequest,
) -> Result[Response, OpenAIFailure] {
  try {
    let (generated, body) = response_parts(request, false)
    self.send_with(
      @gen.create_response_request(generated).body_json(body),
      decode_reader((bytes, head) => decode_response(head, bytes)),
      "responses",
    )
  } catch {
    ValidationError(message~) => Err(Validation(message))
    error => raise error
  }
}

///|
fn decode_stream_data(
  head : @http.ResponseHead,
  data : String,
) -> ResponseEvent raise DecodeError {
  let body = @utf8.encode(data)
  decode_response_event(parse_json_body(body)) catch {
    error => raise decode_error(head, message=error.to_string(), body~)
  }
}

///|
/// Dispatches Responses events in wire order, closing on EOF, [DONE] or failure.
/// User callback failures happen outside the retry attempt.
pub async fn OpenAI::stream_response(
  self : OpenAI,
  request : ResponseRequest,
  f : async (ResponseEvent) -> Unit,
) -> Result[Unit, OpenAIFailure] {
  try {
    let (generated, body) = response_parts(request, true)
    self.send_with(
      @gen.create_response_request(generated)
      .header("accept", "text/event-stream")
      .body_json(body),
      @client.from_head(head => {
        @client.as_sse(event => f(decode_stream_data(head, event.data)), stop=event => {
          event.data == "[DONE]"
        })
      }),
      "responses",
    )
  } catch {
    ValidationError(message~) => Err(Validation(message))
    error => raise error
  }
}