///|
/// A request to the `/moderations` endpoint.
pub(all) struct ModerationRequest {
  input : EmbeddingInput
  mut model : String?
}

///|
/// Create a moderation request for a single text.
pub fn ModerationRequest::of_text(text : String) -> ModerationRequest {
  { input: Single(text), model: None }
}

///|
/// Create a moderation request for a batch of texts.
pub fn ModerationRequest::of_batch(texts : Array[String]) -> ModerationRequest {
  { input: Batch(texts), model: None }
}

///|
/// Set the moderation model explicitly.
pub fn ModerationRequest::model(
  self : ModerationRequest,
  m : String,
) -> ModerationRequest {
  self.model = Some(m)
  self
}

///|
pub impl ToJson for ModerationRequest with fn to_json(self : ModerationRequest) -> Json {
  let obj : Map[String, Json] = { "input": self.input.to_json() }
  if self.model is Some(m) {
    obj["model"] = Json::string(m)
  }
  Json::object(obj)
}

///|
/// A single moderation result: whether the input was flagged, and which
/// categories were triggered.
pub(all) struct ModerationResult {
  flagged : Bool
  categories : Map[String, Bool]
  category_scores : Map[String, Double]
} derive(Debug)

///|
pub impl @json.FromJson for ModerationResult with fn from_json(
  json : Json,
  path : @json.JsonPath,
) -> ModerationResult {
  guard json is Object(obj) else {
    raise @json.JsonDecodeError((path, "ModerationResult: expected object"))
  }
  let flagged = match obj.get("flagged") {
    Some(True) => true
    _ => false
  }
  let categories = {}
  match obj.get("categories") {
    Some(Object(cats)) =>
      for k, v in cats {
        match v {
          True => categories[k] = true
          False => categories[k] = false
          _ => ()
        }
      }
    _ => ()
  }
  let category_scores = {}
  match obj.get("category_scores") {
    Some(Object(scores)) =>
      for k, v in scores {
        match v {
          Number(n, ..) => category_scores[k] = n
          _ => ()
        }
      }
    _ => ()
  }
  { flagged, categories, category_scores }
}

///|
/// The categories that were flagged, in no particular order.
pub fn ModerationResult::flagged_categories(
  self : ModerationResult,
) -> Array[String] {
  let out = []
  for k, v in self.categories {
    if v {
      out.push(k)
    }
  }
  out
}

///|
/// The response from the `/moderations` endpoint.
pub(all) struct ModerationResponse {
  model : String
  results : Array[ModerationResult]
} derive(Debug)

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

///|
/// Whether any input in the batch was flagged.
pub fn ModerationResponse::any_flagged(self : ModerationResponse) -> Bool {
  for r in self.results {
    if r.flagged {
      return true
    }
  }
  false
}

///|
/// Perform a moderation request.
pub async fn Client::moderations(
  self : Client,
  request : ModerationRequest,
) -> ModerationResponse raise LLMError {
  let json = self.post_json("/moderations", request.to_json())
  @json.from_json(json) catch {
    err => raise Decode(err.to_string())
  }
}