///|
/// Why one URL in a `/contents` request did not come back with content.
pub(all) struct ContentStatus {
  id : String
  /// `"success"` or `"error"`.
  status : String
  /// Exa's error tag, e.g. `CRAWL_NOT_FOUND` or `CRAWL_LIVECRAWL_TIMEOUT`.
  error_tag : String?
  http_status_code : Int?
} derive(Eq, @debug.Debug)

///|
/// Build a status directly, for testing code that branches on one.
pub fn ContentStatus::new(
  id : String,
  status? : String = "success",
  error_tag? : String,
  http_status_code? : Int,
) -> ContentStatus {
  { id, status, error_tag, http_status_code, }
}

///|
fn ContentStatus::decode(json : Json) -> ContentStatus {
  let error = field(json, "error")
  {
    id: get_str(json, "id").unwrap_or(""),
    status: get_str(json, "status").unwrap_or("unknown"),
    error_tag: error.bind(fn(e) { get_str(e, "tag") }),
    http_status_code: error.bind(fn(e) { get_int(e, "httpStatusCode") }),
  }
}

///|
/// True when this URL was crawled successfully.
pub fn ContentStatus::is_success(self : ContentStatus) -> Bool {
  self.status == "success"
}

///|
/// The result of a `/contents` call.
pub(all) struct ContentsResponse {
  request_id : String?
  results : Array[SearchResult]
  /// One entry per requested URL, including the ones that failed. Check this
  /// rather than assuming `results` lines up with the URLs you asked for.
  statuses : Array[ContentStatus]
  cost_dollars : CostDollars?
  raw : Json
} derive(Eq, @debug.Debug)

///|
/// Build a response directly, for testing code that consumes one.
pub fn ContentsResponse::new(
  results? : Array[SearchResult] = [],
  statuses? : Array[ContentStatus] = [],
  request_id? : String,
  cost_dollars? : CostDollars,
  raw? : Json = Json::null(),
) -> ContentsResponse {
  { request_id, results, statuses, cost_dollars, raw, }
}

///|
fn ContentsResponse::decode(json : Json) -> ContentsResponse raise ExaError {
  let statuses = []
  for item in get_array(json, "statuses") {
    statuses.push(ContentStatus::decode(item))
  }
  {
    request_id: get_str(json, "requestId"),
    results: decode_results(json, "contents result"),
    statuses,
    cost_dollars: field(json, "costDollars").map(CostDollars::decode),
    raw: json,
  }
}

///|
/// Fetch the contents of URLs you already have.
///
/// Ask for at least one of `text`, `highlights` or `summary` — with none of
/// them set, Exa has nothing to return. URLs that fail to crawl do not fail the
/// call; they show up in `statuses` instead.
pub async fn Client::contents(
  self : Client,
  urls : Array[String],
  text? : Text,
  highlights? : Highlights,
  summary? : Summary,
  livecrawl_timeout? : Int,
  max_age_hours? : Int,
  subpages? : Int,
  subpage_target? : SubpageTarget,
  extras? : ExtrasOptions,
) -> ContentsResponse {
  let body = JsonObject::new()
  body.set("urls", urls.to_json())
  ContentsOptions::new(
    text?,
    highlights?,
    summary?,
    livecrawl_timeout?,
    max_age_hours?,
    subpages?,
    subpage_target?,
    extras?,
  ).write_into(body)
  ContentsResponse::decode(self.post_json("/contents", body.build()))
}