// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// 移植自 milvus-io/milvus client/milvusclient/{collection.go,
// collection_options.go}(Apache-2.0)。
//
// 上游的 `CreateCollection` 在成功之后还会顺带建索引、必要时 load 集合
// (`IsFast()`),本移植只保留 SDK 层面的「发一次 CreateCollection」,
// 建索引/load 属于后续 Issue,不在这里假装做完。

///|
/// 建集合的入参。对应上游 `createCollectionOption`。
///
/// `schema` 为 `None` 时就是「快速建表」:按 `dim` 合成一张
/// 主键 + 向量字段的默认表,字段名与上游
/// `SimpleCreateCollectionOptions` 一致(`id` / `vector`)。
pub(all) struct CreateCollectionOption {
  name : String
  shard_num : Int
  schema : @entity.CollectionSchema?
  dim : Int
  auto_id : Bool
  enable_dynamic_schema : Bool
  pk_field_name : String
  vector_field_name : String
  varchar_pk : Bool
  varchar_pk_max_length : Int
  consistency_level : ConsistencyLevel
  properties : Array[(String, String)]
  num_partitions : Int64
}

///|
/// 上游 `NewCreateCollectionOption`:拿一张自定义 schema 建表。
/// 默认分片数 1、一致性 `Bounded`。
pub fn new_create_collection_option(
  name : String,
  schema : @entity.CollectionSchema,
) -> CreateCollectionOption {
  {
    name,
    shard_num: 1,
    schema: Some(schema),
    dim: 0,
    auto_id: false,
    enable_dynamic_schema: false,
    pk_field_name: "id",
    vector_field_name: "vector",
    varchar_pk: false,
    varchar_pk_max_length: 0,
    consistency_level: default_consistency_level,
    properties: [],
    num_partitions: 0L,
  }
}

///|
/// 上游 `SimpleCreateCollectionOptions`:只给名字和维度,其余按默认。
pub fn simple_create_collection_option(
  name : String,
  dim : Int,
) -> CreateCollectionOption {
  {
    name,
    shard_num: 1,
    schema: None,
    dim,
    auto_id: true,
    enable_dynamic_schema: true,
    pk_field_name: "id",
    vector_field_name: "vector",
    varchar_pk: false,
    varchar_pk_max_length: 0,
    consistency_level: default_consistency_level,
    properties: [],
    num_partitions: 0L,
  }
}

///|
pub fn CreateCollectionOption::with_auto_id(
  self : CreateCollectionOption,
  auto_id : Bool,
) -> CreateCollectionOption {
  { ..self, auto_id, }
}

///|
pub fn CreateCollectionOption::with_shard_num(
  self : CreateCollectionOption,
  shard_num : Int,
) -> CreateCollectionOption {
  { ..self, shard_num, }
}

///|
pub fn CreateCollectionOption::with_dynamic_schema(
  self : CreateCollectionOption,
  enable? : Bool = true,
) -> CreateCollectionOption {
  { ..self, enable_dynamic_schema: enable, }
}

///|
pub fn CreateCollectionOption::with_varchar_pk(
  self : CreateCollectionOption,
  varchar_pk : Bool,
  max_length : Int,
) -> CreateCollectionOption {
  { ..self, varchar_pk, varchar_pk_max_length: max_length, }
}

///|
pub fn CreateCollectionOption::with_pk_field_name(
  self : CreateCollectionOption,
  name : String,
) -> CreateCollectionOption {
  { ..self, pk_field_name: name, }
}

///|
pub fn CreateCollectionOption::with_vector_field_name(
  self : CreateCollectionOption,
  name : String,
) -> CreateCollectionOption {
  { ..self, vector_field_name: name, }
}

///|
pub fn CreateCollectionOption::with_consistency_level(
  self : CreateCollectionOption,
  level : ConsistencyLevel,
) -> CreateCollectionOption {
  { ..self, consistency_level: level, }
}

///|
pub fn CreateCollectionOption::with_num_partitions(
  self : CreateCollectionOption,
  num_partitions : Int64,
) -> CreateCollectionOption {
  { ..self, num_partitions, }
}

///|
/// 追加一条集合属性。同名键覆盖已有的。
pub fn CreateCollectionOption::with_property(
  self : CreateCollectionOption,
  key : String,
  value : String,
) -> CreateCollectionOption {
  let properties : Array[(String, String)] = []
  let mut replaced = false
  for pair in self.properties {
    if pair.0 == key {
      properties.push((key, value))
      replaced = true
    } else {
      properties.push(pair)
    }
  }
  if !replaced {
    properties.push((key, value))
  }
  { ..self, properties, }
}

///|
/// 快速建表时合成的那张 schema,与上游 `Request()` 里 `isFast` 分支一致。
fn fast_schema(
  option : CreateCollectionOption,
) -> @entity.CollectionSchema raise ClientError {
  let pk = if option.varchar_pk {
    @entity.Field::new(option.pk_field_name, @entity.DataType::VarChar).with_max_length(
      option.varchar_pk_max_length,
    ) catch {
      err => raise ClientError::Schema(entity_schema_message(err))
    }
  } else {
    @entity.Field::new(option.pk_field_name, @entity.DataType::Int64)
  }
  let mut pk = pk.as_primary_key()
  if option.auto_id {
    pk = pk.as_auto_id()
  }
  let mut vector = @entity.Field::new(
    option.vector_field_name,
    @entity.DataType::FloatVector,
  )
  vector = vector.with_dim(option.dim) catch {
    err => raise ClientError::Schema(entity_schema_message(err))
  }
  @entity.CollectionSchema::new([pk, vector]).with_dynamic_field(
    option.enable_dynamic_schema,
  )
}

///|
/// 组装 `CreateCollectionRequest`。
fn create_collection_request(
  option : CreateCollectionOption,
) -> @milvus.CreateCollectionRequest raise ClientError {
  let schema_proto = match option.schema {
    Some(schema) => serialize_collection_schema(option.name, schema)
    None => serialize_collection_schema(option.name, fast_schema(option))
  }
  let properties = option.properties.map(pair => {
    @common.KeyValuePair::KeyValuePair(pair.0, pair.1)
  })
  @milvus.CreateCollectionRequest::{
    base: None,
    db_name: "",
    collection_name: option.name,
    schema: schema_proto,
    shards_num: option.shard_num,
    consistency_level: option.consistency_level.to_proto(),
    properties,
    num_partitions: option.num_partitions,
  }
}

///|
/// 建集合。schema 不合法会在发出请求之前就报 `ClientError::Schema`。
///
/// 上游还在这里断言 `Validate() error`(客户端预检)。本移植把这一步
/// 显式化:schema 走一遍 `@entity.CollectionSchema::validate`,
/// 快速建表也走,因为合成出来的表同样可能不合法(比如 dim 为 0)。
pub async fn Client::create_collection(
  self : Client,
  option : CreateCollectionOption,
) -> Unit raise ClientError {
  let schema = match option.schema {
    Some(schema) => schema
    None => fast_schema(option)
  }
  schema.validate() catch {
    err => raise ClientError::Schema(entity_schema_message(err))
  }
  let request = create_collection_request(option)
  let response : @common.Status = self.call_service(
    create_collection_path, request,
  )
  match check_status(Some(response)) {
    Some(err) => raise err
    None => ()
  }
}

///|
/// 描述集合的入参。
pub(all) struct DescribeCollectionOption {
  name : String
}

///|
pub fn new_describe_collection_option(
  name : String,
) -> DescribeCollectionOption {
  { name, }
}

///|
/// 集合的元信息。对应上游 `entity.Collection` 里 SDK 层面的那几项。
pub(all) struct CollectionDescription {
  name : String
  id : Int64
  schema : @entity.CollectionSchema
  physical_channels : Array[String]
  virtual_channels : Array[String]
  consistency_level : ConsistencyLevel
  shard_num : Int
  properties : Array[(String, String)]
  update_timestamp : UInt64
} derive(Debug)

///|
/// 把响应翻成 `CollectionDescription`。
///
/// 集合名以 `schema.name` 为准(上游也是 `collection.Name =
/// collection.Schema.CollectionName`),响应顶层的 `collection_name` 只作兜底。
fn collection_description_from_proto(
  response : @milvus.DescribeCollectionResponse,
) -> CollectionDescription raise ClientError {
  let schema_proto = response.schema.unwrap()
  let fields : Array[@entity.Field] = []
  for field in schema_proto.fields {
    fields.push(field_from_proto(field))
  }
  let schema = @entity.CollectionSchema::new(fields)
    .with_description(schema_proto.description)
    .with_dynamic_field(schema_proto.enable_dynamic_field)
  let properties : Array[(String, String)] = response.properties.map(pair => {
    (pair.key, pair.value)
  })
  {
    name: if schema_proto.name != "" {
      schema_proto.name
    } else {
      response.collection_name
    },
    id: response.collection_id,
    schema,
    physical_channels: response.physical_channel_names,
    virtual_channels: response.virtual_channel_names,
    consistency_level: ConsistencyLevel::from_proto(response.consistency_level),
    shard_num: response.shards_num,
    properties,
    update_timestamp: response.update_timestamp,
  }
}

///|
/// 查集合元信息。
pub async fn Client::describe_collection(
  self : Client,
  option : DescribeCollectionOption,
) -> CollectionDescription raise ClientError {
  let request = @milvus.DescribeCollectionRequest::{
    base: None,
    db_name: "",
    collection_name: option.name,
    collection_id: 0L,
    time_stamp: 0UL,
  }
  let response : @milvus.DescribeCollectionResponse = self.call_service(
    describe_collection_path, request,
  )
  match check_status(response.status) {
    Some(err) => raise err
    None => collection_description_from_proto(response)
  }
}

///|
/// 集合是否存在。
///
/// 上游用 `DescribeCollection` 实现,把 `ErrCollectionNotFound` 当成
/// 「不存在」而不是失败 —— 别指望用 `HasCollection` RPC,
/// 上游的 `HasCollection` 也是这么走的。
pub async fn Client::has_collection(
  self : Client,
  option : DescribeCollectionOption,
) -> Bool raise ClientError {
  let request = @milvus.DescribeCollectionRequest::{
    base: None,
    db_name: "",
    collection_name: option.name,
    collection_id: 0L,
    time_stamp: 0UL,
  }
  let response : @milvus.DescribeCollectionResponse = self.call_service(
    describe_collection_path, request,
  )
  match check_status(response.status) {
    Some(Server(err)) =>
      if @errors.error_code(Some(err)) ==
        @errors.error_code(Some(@errors.err_collection_not_found())) {
        false
      } else {
        raise ClientError::Server(err)
      }
    Some(other) => raise other
    None => true
  }
}

///|
/// 删集合的入参。
pub(all) struct DropCollectionOption {
  name : String
}

///|
pub fn new_drop_collection_option(name : String) -> DropCollectionOption {
  { name, }
}

///|
/// 删集合,连带它的数据一起删。
pub async fn Client::drop_collection(
  self : Client,
  option : DropCollectionOption,
) -> Unit raise ClientError {
  let request = @milvus.DropCollectionRequest::{
    base: None,
    db_name: "",
    collection_name: option.name,
  }
  let response : @common.Status = self.call_service(
    drop_collection_path, request,
  )
  match check_status(Some(response)) {
    Some(err) => raise err
    None => ()
  }
}

///|
/// 列集合的入参。上游就一个空请求,这里保留一个类型,
/// 以便将来加 `type` / `db_name` 时不动调用点。
pub(all) struct ListCollectionsOption {
  show_loaded : Bool
}

///|
pub fn new_list_collections_option() -> ListCollectionsOption {
  { show_loaded: false, }
}

///|
/// 列出库里的集合名。
pub async fn Client::list_collections(
  self : Client,
  option : ListCollectionsOption,
) -> Array[String] raise ClientError {
  let request = @milvus.ShowCollectionsRequest::{
    base: None,
    db_name: "",
    time_stamp: 0UL,
    type_: if option.show_loaded {
      @milvus.ShowType::InMemory
    } else {
      @milvus.ShowType::All
    },
    collection_names: [],
  }
  let response : @milvus.ShowCollectionsResponse = self.call_service(
    show_collections_path, request,
  )
  match check_status(response.status) {
    Some(err) => raise err
    None => response.collection_names
  }
}