///|
/// A request to create a batch job (`POST /batches`).
pub(all) struct BatchRequest {
  input_file_id : String
  endpoint : String
  completion_window : String
}

///|
/// Create a batch request. `endpoint` is the API path the batch runs against
/// (e.g. `/v1/chat/completions`); `completion_window` is typically `"24h"`.
pub fn BatchRequest::new(
  input_file_id : String,
  endpoint? : String = "/v1/chat/completions",
  completion_window? : String = "24h",
) -> BatchRequest {
  { input_file_id, endpoint, completion_window }
}

///|
pub impl ToJson for BatchRequest with fn to_json(self : BatchRequest) -> Json {
  Json::object({
    "input_file_id": Json::string(self.input_file_id),
    "endpoint": Json::string(self.endpoint),
    "completion_window": Json::string(self.completion_window),
  })
}

///|
/// Per-request counts for a batch job.
pub(all) struct BatchCounts {
  total : Int
  completed : Int
  failed : Int
} derive(Debug)

///|
/// A batch job object.
pub(all) struct Batch {
  id : String
  status : String
  input_file_id : String
  output_file_id : String?
  error_file_id : String?
  counts : BatchCounts
} derive(Debug)

///|
pub impl @json.FromJson for Batch with fn from_json(
  json : Json,
  path : @json.JsonPath,
) -> Batch {
  guard json is Object(obj) else {
    raise @json.JsonDecodeError((path, "Batch: expected object"))
  }
  let id = match obj.get("id") {
    Some(String(s)) => s
    _ => ""
  }
  let status = match obj.get("status") {
    Some(String(s)) => s
    _ => ""
  }
  let input_file_id = match obj.get("input_file_id") {
    Some(String(s)) => s
    _ => ""
  }
  let output_file_id = match obj.get("output_file_id") {
    Some(String(s)) => Some(s)
    _ => None
  }
  let error_file_id = match obj.get("error_file_id") {
    Some(String(s)) => Some(s)
    _ => None
  }
  let counts = match obj.get("request_counts") {
    Some(Object(c)) => {
      let total = match c.get("total") {
        Some(Number(n, ..)) => n.to_int()
        _ => 0
      }
      let completed = match c.get("completed") {
        Some(Number(n, ..)) => n.to_int()
        _ => 0
      }
      let failed = match c.get("failed") {
        Some(Number(n, ..)) => n.to_int()
        _ => 0
      }
      { total, completed, failed }
    }
    _ => { total: 0, completed: 0, failed: 0 }
  }
  { id, status, input_file_id, output_file_id, error_file_id, counts }
}

///|
/// Whether the batch has reached a terminal state.
pub fn Batch::is_terminal(self : Batch) -> Bool {
  match self.status {
    "completed" | "failed" | "expired" | "cancelled" => true
    _ => false
  }
}

///|
/// Create a batch job (`POST /batches`).
pub async fn Client::create_batch(
  self : Client,
  request : BatchRequest,
) -> Batch raise LLMError {
  let json = self.post_json("/batches", request.to_json())
  @json.from_json(json) catch {
    err => raise Decode(err.to_string())
  }
}

///|
/// Retrieve a batch job's status (`GET /batches/{id}`).
pub async fn Client::batch(self : Client, id : String) -> Batch raise LLMError {
  let json = self.get_json("/batches/" + id)
  @json.from_json(json) catch {
    err => raise Decode(err.to_string())
  }
}

///|
/// Cancel a batch job (`POST /batches/{id}/cancel`).
pub async fn Client::cancel_batch(
  self : Client,
  id : String,
) -> Batch raise LLMError {
  let json = self.post_json("/batches/" + id + "/cancel", Json::object({}))
  @json.from_json(json) catch {
    err => raise Decode(err.to_string())
  }
}