// Cloudflare Vectorize API bindings

///|
/// VectorizeIndex - represents a Vectorize index binding
#external
pub type VectorizeIndex

///|
pub fn VectorizeIndex::as_any(self : VectorizeIndex) -> @core.Any = "%identity"

///|
/// Insert vectors into the index
/// Vectors with existing IDs will be ignored (use upsert to overwrite)
pub async fn VectorizeIndex::insert(
  self : VectorizeIndex,
  vectors : Array[VectorizeVector],
) -> VectorizeMutationResult {
  let vectors_js = @core.new_array()
  let mut i = 0
  while i < vectors.length() {
    @core.any(vectors_js)._call("push", [vectors[i].to_js()]) |> ignore
    i = i + 1
  }
  let promise : @core.Promise[VectorizeMutationResult] = self
    .as_any()
    ._call("insert", [@core.any(vectors_js)])
    .cast()
  promise.wait()
}

///|
/// Upsert vectors into the index
/// Vectors with existing IDs will be completely overwritten
pub async fn VectorizeIndex::upsert(
  self : VectorizeIndex,
  vectors : Array[VectorizeVector],
) -> VectorizeMutationResult {
  let vectors_js = @core.new_array()
  let mut i = 0
  while i < vectors.length() {
    @core.any(vectors_js)._call("push", [vectors[i].to_js()]) |> ignore
    i = i + 1
  }
  let promise : @core.Promise[VectorizeMutationResult] = self
    .as_any()
    ._call("upsert", [@core.any(vectors_js)])
    .cast()
  promise.wait()
}

///|
/// Delete vectors by their IDs
pub async fn VectorizeIndex::delete_by_ids(
  self : VectorizeIndex,
  ids : Array[String],
) -> VectorizeMutationResult {
  let ids_js = @core.new_array()
  let mut i = 0
  while i < ids.length() {
    @core.any(ids_js)._call("push", [@core.any(ids[i])]) |> ignore
    i = i + 1
  }
  let promise : @core.Promise[VectorizeMutationResult] = self
    .as_any()
    ._call("deleteByIds", [@core.any(ids_js)])
    .cast()
  promise.wait()
}

///|
/// Query the index with a vector
pub async fn VectorizeIndex::query(
  self : VectorizeIndex,
  vector : Array[Double],
  options : VectorizeQueryOptions,
) -> VectorizeMatches {
  let vector_js = @core.new_array()
  let mut i = 0
  while i < vector.length() {
    @core.any(vector_js)._call("push", [@core.any(vector[i])]) |> ignore
    i = i + 1
  }
  let promise : @core.Promise[VectorizeMatches] = self
    .as_any()
    ._call("query", [@core.any(vector_js), options.to_js()])
    .cast()
  promise.wait()
}

///|
/// Query the index with a vector (no options)
pub async fn VectorizeIndex::query_simple(
  self : VectorizeIndex,
  vector : Array[Double],
) -> VectorizeMatches {
  let vector_js = @core.new_array()
  let mut i = 0
  while i < vector.length() {
    @core.any(vector_js)._call("push", [@core.any(vector[i])]) |> ignore
    i = i + 1
  }
  let promise : @core.Promise[VectorizeMatches] = self
    .as_any()
    ._call("query", [@core.any(vector_js)])
    .cast()
  promise.wait()
}

///|
/// Get vectors by their IDs
pub async fn VectorizeIndex::get_by_ids(
  self : VectorizeIndex,
  ids : Array[String],
) -> Array[VectorizeVector] {
  let ids_js = @core.new_array()
  let mut i = 0
  while i < ids.length() {
    @core.any(ids_js)._call("push", [@core.any(ids[i])]) |> ignore
    i = i + 1
  }
  let promise : @core.Promise[@core.Any] = self
    .as_any()
    ._call("getByIds", [@core.any(ids_js)])
    .cast()
  let result = promise.wait()
  // Convert JS array to MoonBit array of VectorizeVector
  let len : Int = result["length"].cast()
  let vectors : Array[VectorizeVector] = []
  let mut j = 0
  while j < len {
    vectors.push(VectorizeVector::from_js(result[j.to_string()]))
    j = j + 1
  }
  vectors
}

///|
/// Describe the index configuration
pub async fn VectorizeIndex::describe(
  self : VectorizeIndex,
) -> VectorizeIndexInfo {
  let promise : @core.Promise[VectorizeIndexInfo] = self
    .as_any()
    ._call("describe", [])
    .cast()
  promise.wait()
}

///|
/// Vector representation for Vectorize
pub(all) struct VectorizeVector {
  id : String
  values : Array[Double]
  ns : String? // namespace - partition key for isolated segments
  metadata : @core.Any?
}

///|
/// Convert VectorizeVector to JavaScript object
pub fn VectorizeVector::to_js(self : VectorizeVector) -> @core.Any {
  let obj = @core.new_object()
  obj["id"] = @core.any(self.id)

  // Convert values array
  let values_js = @core.new_array()
  let mut i = 0
  while i < self.values.length() {
    @core.any(values_js)._call("push", [@core.any(self.values[i])]) |> ignore
    i = i + 1
  }
  obj["values"] = @core.any(values_js)
  if self.ns is Some(ns_val) {
    obj["namespace"] = @core.any(ns_val)
  }
  if self.metadata is Some(meta) {
    obj["metadata"] = meta
  }
  obj
}

///|
/// Create VectorizeVector from JavaScript object
pub fn VectorizeVector::from_js(js : @core.Any) -> VectorizeVector {
  let id : String = js["id"].cast()

  // Convert values
  let values_js = js["values"]
  let values_len : Int = values_js["length"].cast()
  let values : Array[Double] = []
  let mut i = 0
  while i < values_len {
    values.push(values_js[i.to_string()].cast())
    i = i + 1
  }

  // Handle optional namespace
  let ns_js = js["namespace"]
  let ns : String? = if @core.typeof_(ns_js) == "undefined" ||
    @core.is_null(ns_js) {
    None
  } else {
    Some(ns_js.cast())
  }

  // Handle optional metadata
  let meta_js = js["metadata"]
  let metadata = if @core.typeof_(meta_js) == "undefined" ||
    @core.is_null(meta_js) {
    None
  } else {
    Some(meta_js)
  }
  { id, values, ns, metadata }
}

///|
/// Query options for Vectorize
pub(all) struct VectorizeQueryOptions {
  topK : Int? // Max 100, or 20 if returnValues/returnMetadata is set
  returnValues : Bool?
  returnMetadata : VectorizeReturnMetadata?
  ns : String? // namespace - partition key
  filter : @core.Any? // Metadata filter
}

///|
/// Metadata return options
pub(all) enum VectorizeReturnMetadata {
  None_
  Indexed
  All
}

///|
pub fn VectorizeReturnMetadata::to_string(
  self : VectorizeReturnMetadata,
) -> String {
  match self {
    None_ => "none"
    Indexed => "indexed"
    All => "all"
  }
}

///|
/// Convert VectorizeQueryOptions to JavaScript object
pub fn VectorizeQueryOptions::to_js(self : VectorizeQueryOptions) -> @core.Any {
  let obj = @core.new_object()
  if self.topK is Some(k) {
    obj["topK"] = @core.any(k)
  }
  if self.returnValues is Some(v) {
    obj["returnValues"] = @core.any(v)
  }
  if self.returnMetadata is Some(rm) {
    obj["returnMetadata"] = @core.any(rm.to_string())
  }
  if self.ns is Some(ns_val) {
    obj["namespace"] = @core.any(ns_val)
  }
  if self.filter is Some(f) {
    obj["filter"] = f
  }
  obj
}

///|
/// Default query options
pub fn VectorizeQueryOptions::default() -> VectorizeQueryOptions {
  {
    topK: None,
    returnValues: None,
    returnMetadata: None,
    ns: None,
    filter: None,
  }
}

///|
/// Query result containing matched vectors
#external
pub type VectorizeMatches

///|
pub fn VectorizeMatches::as_any(self : VectorizeMatches) -> @core.Any = "%identity"

///|
/// Get the matched vectors
pub fn VectorizeMatches::matches(
  self : VectorizeMatches,
) -> Array[VectorizeMatch] {
  let matches_js = self.as_any()["matches"]
  let len : Int = matches_js["length"].cast()
  let matches : Array[VectorizeMatch] = []
  let mut i = 0
  while i < len {
    matches.push(matches_js[i.to_string()].cast())
    i = i + 1
  }
  matches
}

///|
/// Get the count of matches
pub fn VectorizeMatches::count(self : VectorizeMatches) -> Int {
  self.as_any()["count"].cast()
}

///|
/// A single match result
#external
pub type VectorizeMatch

///|
pub fn VectorizeMatch::as_any(self : VectorizeMatch) -> @core.Any = "%identity"

///|
/// Get the vector ID
pub fn VectorizeMatch::id(self : VectorizeMatch) -> String {
  self.as_any()["id"].cast()
}

///|
/// Get the similarity score
pub fn VectorizeMatch::score(self : VectorizeMatch) -> Double {
  self.as_any()["score"].cast()
}

///|
/// Get the vector values (if returnValues was true)
pub fn VectorizeMatch::values(self : VectorizeMatch) -> Array[Double]? {
  let values_js = self.as_any()["values"]
  if @core.typeof_(values_js) == "undefined" || @core.is_null(values_js) {
    None
  } else {
    let len : Int = values_js["length"].cast()
    let values : Array[Double] = []
    let mut i = 0
    while i < len {
      values.push(values_js[i.to_string()].cast())
      i = i + 1
    }
    Some(values)
  }
}

///|
/// Get the metadata (if returnMetadata was set)
pub fn VectorizeMatch::metadata(self : VectorizeMatch) -> @core.Any? {
  let meta_js = self.as_any()["metadata"]
  if @core.typeof_(meta_js) == "undefined" || @core.is_null(meta_js) {
    None
  } else {
    Some(meta_js)
  }
}

///|
/// Get the namespace
pub fn VectorizeMatch::ns(self : VectorizeMatch) -> String? {
  let ns_js = self.as_any()["namespace"]
  if @core.typeof_(ns_js) == "undefined" || @core.is_null(ns_js) {
    None
  } else {
    Some(ns_js.cast())
  }
}

///|
/// Result of mutation operations (insert, upsert, delete)
#external
pub type VectorizeMutationResult

///|
pub fn VectorizeMutationResult::as_any(
  self : VectorizeMutationResult,
) -> @core.Any = "%identity"

///|
/// Get the mutation ID
pub fn VectorizeMutationResult::mutation_id(
  self : VectorizeMutationResult,
) -> String {
  self.as_any()["mutationId"].cast()
}

///|
/// Get the count of affected vectors
pub fn VectorizeMutationResult::count(self : VectorizeMutationResult) -> Int {
  self.as_any()["count"].cast()
}

///|
/// Get the IDs of affected vectors
pub fn VectorizeMutationResult::ids(
  self : VectorizeMutationResult,
) -> Array[String] {
  let ids_js = self.as_any()["ids"]
  if @core.typeof_(ids_js) == "undefined" || @core.is_null(ids_js) {
    []
  } else {
    let len : Int = ids_js["length"].cast()
    let ids : Array[String] = []
    let mut i = 0
    while i < len {
      ids.push(ids_js[i.to_string()].cast())
      i = i + 1
    }
    ids
  }
}

///|
/// Index configuration information
#external
pub type VectorizeIndexInfo

///|
pub fn VectorizeIndexInfo::as_any(self : VectorizeIndexInfo) -> @core.Any = "%identity"

///|
/// Get the number of dimensions
pub fn VectorizeIndexInfo::dimensions(self : VectorizeIndexInfo) -> Int {
  self.as_any()["dimensions"].cast()
}

///|
/// Get the distance metric (e.g., "cosine", "euclidean", "dot-product")
pub fn VectorizeIndexInfo::metric(self : VectorizeIndexInfo) -> String {
  self.as_any()["metric"].cast()
}

///|
/// Get the total number of vectors in the index
pub fn VectorizeIndexInfo::vector_count(self : VectorizeIndexInfo) -> Int {
  let vc = self.as_any()["vectorCount"]
  if @core.typeof_(vc) == "undefined" || @core.is_null(vc) {
    0
  } else {
    vc.cast()
  }
}