///|
/// A request to the `/images/generations` endpoint.
pub(all) struct ImageRequest {
  prompt : String
  mut model : String?
  mut n : Int?
  mut size : String?
  mut quality : String?
  /// Response format: `"url"` or `"b64_json"`.
  mut response_format : String?
}

///|
/// Create an image generation request from a prompt.
pub fn ImageRequest::new(prompt : String) -> ImageRequest {
  {
    prompt,
    model: None,
    n: None,
    size: None,
    quality: None,
    response_format: None,
  }
}

///|
pub fn ImageRequest::model(self : ImageRequest, m : String) -> ImageRequest {
  self.model = Some(m)
  self
}

///|
pub fn ImageRequest::n(self : ImageRequest, count : Int) -> ImageRequest {
  self.n = Some(count)
  self
}

///|
/// Set the output size, e.g. `"1024x1024"`.
pub fn ImageRequest::size(self : ImageRequest, s : String) -> ImageRequest {
  self.size = Some(s)
  self
}

///|
pub fn ImageRequest::quality(self : ImageRequest, q : String) -> ImageRequest {
  self.quality = Some(q)
  self
}

///|
pub impl ToJson for ImageRequest with fn to_json(self : ImageRequest) -> Json {
  let obj : Map[String, Json] = { "prompt": Json::string(self.prompt) }
  if self.model is Some(m) {
    obj["model"] = Json::string(m)
  }
  if self.n is Some(count) {
    obj["n"] = Json::number(count.to_double())
  }
  if self.size is Some(s) {
    obj["size"] = Json::string(s)
  }
  if self.quality is Some(q) {
    obj["quality"] = Json::string(q)
  }
  if self.response_format is Some(f) {
    obj["response_format"] = Json::string(f)
  }
  Json::object(obj)
}

///|
/// A single generated image: either a URL or base64-encoded data.
pub(all) struct GeneratedImage {
  url : String?
  b64_json : String?
  revised_prompt : String?
} derive(Debug)

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

///|
/// A response from the `/images/generations` endpoint.
pub(all) struct ImageResponse {
  created : Int64
  data : Array[GeneratedImage]
} derive(Debug)

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

///|
/// All non-empty image URLs in the response.
pub fn ImageResponse::urls(self : ImageResponse) -> Array[String] {
  let out = []
  for img in self.data {
    if img.url is Some(u) {
      out.push(u)
    }
  }
  out
}

///|
/// Generate one or more images from a text prompt.
pub async fn Client::generate_image(
  self : Client,
  request : ImageRequest,
) -> ImageResponse raise LLMError {
  let json = self.post_json("/images/generations", request.to_json())
  @json.from_json(json) catch {
    err => raise Decode(err.to_string())
  }
}