///|
/// Which search strategy Exa should use. `Auto` lets Exa pick.
pub(all) enum SearchType {
  Auto
  Fast
  Instant
  DeepLite
  Deep
  DeepReasoning
}

///|
pub impl ToJson for SearchType with fn to_json(self) {
  match self {
    Auto => "auto".to_json()
    Fast => "fast".to_json()
    Instant => "instant".to_json()
    DeepLite => "deep-lite".to_json()
    Deep => "deep".to_json()
    DeepReasoning => "deep-reasoning".to_json()
  }
}

///|
/// Restrict a search to one kind of page.
pub(all) enum Category {
  Company
  People
  Publication
  News
  PersonalSite
  FinancialReport
}

///|
pub impl ToJson for Category with fn to_json(self) {
  match self {
    Company => "company".to_json()
    People => "people".to_json()
    Publication => "publication".to_json()
    News => "news".to_json()
    PersonalSite => "personal site".to_json()
    FinancialReport => "financial report".to_json()
  }
}

///|
/// The result of a `/search` call.
pub(all) struct SearchResponse {
  request_id : String?
  results : Array[SearchResult]
  /// Present only when the request supplied an `output_schema`.
  output : SynthesisOutput?
  cost_dollars : CostDollars?
  /// Server-side processing time in milliseconds, measured at the gateway.
  search_time : Double?
  raw : Json
} derive(Eq, @debug.Debug)

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

///|
fn SearchResponse::decode(json : Json) -> SearchResponse raise ExaError {
  {
    request_id: get_str(json, "requestId"),
    results: decode_results(json, "search result"),
    output: field(json, "output").map(SynthesisOutput::decode),
    cost_dollars: field(json, "costDollars").map(CostDollars::decode),
    search_time: get_double(json, "searchTime"),
    raw: json,
  }
}

///|
/// Search the web.
///
/// `query` is a natural-language description of what you are looking for —
/// Exa's index is embedding-based, so long descriptive queries work better than
/// keywords. Pass `contents` to get page text, highlights or summaries back
/// with the results instead of making a second `/contents` call.
///
/// Note that `search_type` maps to the API's `type` field, which is a reserved
/// word in MoonBit.
///
/// Raises `ExaError::Api` if Exa rejects the request, and whatever the
/// `Transport` raises if the request never got there.
pub async fn Client::search(
  self : Client,
  query : String,
  search_type? : SearchType,
  num_results? : Int,
  category? : Category,
  user_location? : String,
  include_domains? : Array[String],
  exclude_domains? : Array[String],
  start_published_date? : String,
  end_published_date? : String,
  start_crawl_date? : String,
  end_crawl_date? : String,
  moderation? : Bool,
  additional_queries? : Array[String],
  system_prompt? : String,
  output_schema? : Json,
  contents? : ContentsOptions,
) -> SearchResponse {
  let body = JsonObject::new()
  body.set("query", query.to_json())
  body.set_opt("type", search_type)
  body.set_opt("numResults", num_results)
  body.set_opt("category", category)
  body.set_opt("userLocation", user_location)
  body.set_opt("includeDomains", include_domains)
  body.set_opt("excludeDomains", exclude_domains)
  body.set_opt("startPublishedDate", start_published_date)
  body.set_opt("endPublishedDate", end_published_date)
  body.set_opt("startCrawlDate", start_crawl_date)
  body.set_opt("endCrawlDate", end_crawl_date)
  body.set_opt("moderation", moderation)
  body.set_opt("additionalQueries", additional_queries)
  body.set_opt("systemPrompt", system_prompt)
  body.set_opt("outputSchema", output_schema)
  body.set_opt("contents", contents)
  SearchResponse::decode(self.post_json("/search", body.build()))
}