///|
/// A request to create a fine-tuning job (`POST /fine_tuning/jobs`).
pub(all) struct FineTuneRequest {
  model : String
  training_file : String
  mut validation_file : String?
  mut suffix : String?
  mut n_epochs : Int?
}

///|
/// Create a fine-tuning request from a base model and a training file id.
pub fn FineTuneRequest::new(
  model : String,
  training_file : String,
) -> FineTuneRequest {
  { model, training_file, validation_file: None, suffix: None, n_epochs: None }
}

///|
/// Set the validation file id.
pub fn FineTuneRequest::validation_file(
  self : FineTuneRequest,
  id : String,
) -> FineTuneRequest {
  self.validation_file = Some(id)
  self
}

///|
/// Set a suffix for the resulting model name.
pub fn FineTuneRequest::suffix(
  self : FineTuneRequest,
  s : String,
) -> FineTuneRequest {
  self.suffix = Some(s)
  self
}

///|
/// Set the number of training epochs.
pub fn FineTuneRequest::n_epochs(
  self : FineTuneRequest,
  n : Int,
) -> FineTuneRequest {
  self.n_epochs = Some(n)
  self
}

///|
pub impl ToJson for FineTuneRequest with fn to_json(self : FineTuneRequest) -> Json {
  let obj : Map[String, Json] = {
    "model": Json::string(self.model),
    "training_file": Json::string(self.training_file),
  }
  if self.validation_file is Some(v) {
    obj["validation_file"] = Json::string(v)
  }
  if self.suffix is Some(s) {
    obj["suffix"] = Json::string(s)
  }
  if self.n_epochs is Some(n) {
    obj["hyperparameters"] = Json::object({
      "n_epochs": Json::number(n.to_double()),
    })
  }
  Json::object(obj)
}

///|
/// A fine-tuning job object.
pub(all) struct FineTuneJob {
  id : String
  model : String
  status : String
  training_file : String
  fine_tuned_model : String?
} derive(Debug)

///|
pub impl @json.FromJson for FineTuneJob with fn from_json(
  json : Json,
  path : @json.JsonPath,
) -> FineTuneJob {
  guard json is Object(obj) else {
    raise @json.JsonDecodeError((path, "FineTuneJob: expected object"))
  }
  let id = match obj.get("id") {
    Some(String(s)) => s
    _ => ""
  }
  let model = match obj.get("model") {
    Some(String(s)) => s
    _ => ""
  }
  let status = match obj.get("status") {
    Some(String(s)) => s
    _ => ""
  }
  let training_file = match obj.get("training_file") {
    Some(String(s)) => s
    _ => ""
  }
  let fine_tuned_model = match obj.get("fine_tuned_model") {
    Some(String(s)) => Some(s)
    _ => None
  }
  { id, model, status, training_file, fine_tuned_model }
}

///|
/// Whether the job has finished (successfully or not).
pub fn FineTuneJob::is_done(self : FineTuneJob) -> Bool {
  match self.status {
    "succeeded" | "failed" | "cancelled" => true
    _ => false
  }
}

///|
/// A list of fine-tuning jobs.
pub(all) struct FineTuneJobList {
  data : Array[FineTuneJob]
} derive(Debug)

///|
pub impl @json.FromJson for FineTuneJobList with fn from_json(
  json : Json,
  path : @json.JsonPath,
) -> FineTuneJobList {
  guard json is Object(obj) else {
    raise @json.JsonDecodeError((path, "FineTuneJobList: expected object"))
  }
  let data = match obj.get("data") {
    Some(Array(_) as d) => @json.from_json(d)
    _ => []
  }
  { data, }
}

///|
/// Create a fine-tuning job.
pub async fn Client::create_fine_tune(
  self : Client,
  request : FineTuneRequest,
) -> FineTuneJob raise LLMError {
  let json = self.post_json("/fine_tuning/jobs", request.to_json())
  @json.from_json(json) catch {
    err => raise Decode(err.to_string())
  }
}

///|
/// Retrieve a fine-tuning job (`GET /fine_tuning/jobs/{id}`).
pub async fn Client::fine_tune(
  self : Client,
  id : String,
) -> FineTuneJob raise LLMError {
  let json = self.get_json("/fine_tuning/jobs/" + id)
  @json.from_json(json) catch {
    err => raise Decode(err.to_string())
  }
}

///|
/// List fine-tuning jobs (`GET /fine_tuning/jobs`).
pub async fn Client::fine_tunes(
  self : Client,
) -> FineTuneJobList raise LLMError {
  let json = self.get_json("/fine_tuning/jobs")
  @json.from_json(json) catch {
    err => raise Decode(err.to_string())
  }
}

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