// 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/index_options.go 与
// client/index/index.go(Apache-2.0)。
//
// 这一层只做**参数装配**:把 `Index` 与调用方给的名字/字段拼成 RPC 的入参。
// wire 编解码由 `proto/` 生成物负责,两者以这里的数据类型交接。
// 刻意不 import 生成物,这样本 Issue 的验收项(builder 行为、非法组合报错)
// 不依赖生成链条就能测。

///|
/// 索引 RPC 的服务路径,取自 `milvus.proto` 的 `MilvusService`。
/// gRPC 方法名是 `/` + 全限定服务名 + `/` + 方法名。
pub let create_index_path : String = "/milvus.proto.milvus.MilvusService/CreateIndex"

///|
/// 见 `create_index_path`。
pub let describe_index_path : String = "/milvus.proto.milvus.MilvusService/DescribeIndex"

///|
/// 见 `create_index_path`。
pub let drop_index_path : String = "/milvus.proto.milvus.MilvusService/DropIndex"

///|
/// 服务端索引构建状态,取自 `common.IndexState`。
/// 序号必须与上游对齐:这是 wire 上的枚举值,不是客户端概念。
pub(all) enum IndexState {
  None
  Unissued
  InProgress
  Finished
  Failed
  Retry
  /// 上游新增而本客户端尚未认识的值。保留原始序号,不猜测语义。
  Unknown(Int)
} derive(Eq, Debug)

///|
/// 上游枚举序号。
pub fn IndexState::to_int(self : IndexState) -> Int {
  match self {
    None => 0
    Unissued => 1
    InProgress => 2
    Finished => 3
    Failed => 4
    Retry => 5
    Unknown(n) => n
  }
}

///|
/// 反解上游枚举序号。
pub fn IndexState::from_int(n : Int) -> IndexState {
  match n {
    0 => None
    1 => Unissued
    2 => InProgress
    3 => Finished
    4 => Failed
    5 => Retry
    _ => Unknown(n)
  }
}

///|
/// 该状态是否表示构建已结束——成功或失败都算结束,再等下去没有意义。
/// 上游 `CreateIndexTask::Await` 正是在这两个分支上停下的。
pub fn IndexState::is_terminal(self : IndexState) -> Bool {
  match self {
    Finished | Failed => true
    _ => false
  }
}

///|
/// `CreateIndex` 的入参。
pub(all) struct CreateIndexRequest {
  db_name : String
  collection_name : String
  field_name : String
  /// 索引名。空串走服务端默认命名。
  index_name : String
  /// 已装配好的参数键值对,含 `index_type` / `metric_type`。
  extra_params : Array[(String, String)]
} derive(Eq, Debug)

///|
/// 由索引定义拼出 `CreateIndex` 入参。
///
/// `db_name` 留空表示走连接的默认库:Milvus 的 `db_name` 为空时由服务端
/// 落到默认 database,与传 `"default"` 等价但少一次显式依赖。
pub fn CreateIndexRequest::from_index(
  collection_name : String,
  field_name : String,
  index : Index,
  db_name? : String = "",
) -> CreateIndexRequest raise IndexParamError {
  if collection_name == "" {
    raise InvalidArgument(reason="collection_name must not be empty")
  }
  if field_name == "" {
    raise InvalidArgument(reason="field_name must not be empty")
  }
  {
    db_name,
    collection_name,
    field_name,
    index_name: index.name,
    extra_params: index.params.to_pairs(),
  }
}

///|
/// `DescribeIndex` 的入参。`field_name` 与 `index_name` 都留空表示「列全部」。
pub(all) struct DescribeIndexRequest {
  db_name : String
  collection_name : String
  field_name : String
  index_name : String
} derive(Eq, Debug)

///|
/// 列出某集合的全部索引。
pub fn DescribeIndexRequest::list_all(
  collection_name : String,
  db_name? : String = "",
) -> DescribeIndexRequest raise IndexParamError {
  if collection_name == "" {
    raise InvalidArgument(reason="collection_name must not be empty")
  }
  { db_name, collection_name, field_name: "", index_name: "", }
}

///|
/// 按索引名查一个索引。
pub fn DescribeIndexRequest::by_name(
  collection_name : String,
  index_name : String,
  db_name? : String = "",
) -> DescribeIndexRequest raise IndexParamError {
  if collection_name == "" {
    raise InvalidArgument(reason="collection_name must not be empty")
  }
  if index_name == "" {
    raise InvalidArgument(
      reason="index_name must not be empty; use list_all for every index",
    )
  }
  { db_name, collection_name, field_name: "", index_name, }
}

///|
/// 按字段名查该字段上的索引。
pub fn DescribeIndexRequest::by_field(
  collection_name : String,
  field_name : String,
  db_name? : String = "",
) -> DescribeIndexRequest raise IndexParamError {
  if collection_name == "" {
    raise InvalidArgument(reason="collection_name must not be empty")
  }
  if field_name == "" {
    raise InvalidArgument(reason="field_name must not be empty")
  }
  { db_name, collection_name, field_name, index_name: "", }
}

///|
/// `DropIndex` 的入参。
pub(all) struct DropIndexRequest {
  db_name : String
  collection_name : String
  /// 索引名。Milvus 要求给出名字,空串会被服务端拒绝。
  index_name : String
} derive(Eq, Debug)

///|
/// 拼出 `DropIndex` 入参。索引名必填:Milvus 不支持「删该集合的全部索引」,
/// 想全删只能先 `DescribeIndex` 再逐个 drop,所以这里不提供空名入口。
pub fn DropIndexRequest::new(
  collection_name : String,
  index_name : String,
  db_name? : String = "",
) -> DropIndexRequest raise IndexParamError {
  if collection_name == "" {
    raise InvalidArgument(reason="collection_name must not be empty")
  }
  if index_name == "" {
    raise InvalidArgument(reason="index_name must not be empty")
  }
  { db_name, collection_name, index_name, }
}

///|
/// `DescribeIndex` 返回的一条索引描述。
pub(all) struct IndexDescription {
  index_name : String
  index_id : Int64
  /// 就是 `CreateIndex` 时给的键值对,原样回读。
  params : Array[(String, String)]
  field_name : String
  indexed_rows : Int64
  total_rows : Int64
  pending_index_rows : Int64
  state : IndexState
  /// 构建失败时的原因,仅 `state == Failed` 时有意义。
  fail_reason : String
} derive(Eq, Debug)

///|
/// 把回读的参数还原成 `IndexParams`,键名原样。
pub fn IndexDescription::index_params(self : IndexDescription) -> IndexParams {
  IndexParams::from_pairs(self.params)
}

///|
/// 回读的索引类型。上游把 `index_type` 放在 params 里,不是独立字段;
/// 返回 `None` 表示服务端没给这个键(老版本可能不返回)。
pub fn IndexDescription::index_type(self : IndexDescription) -> IndexType? {
  match self.index_params().get(index_type_key) {
    Some(s) => IndexType::from_string(s)
    None => None
  }
}

///|
/// 回读的度量类型。
pub fn IndexDescription::metric_type(self : IndexDescription) -> MetricType? {
  match self.index_params().get(metric_type_key) {
    Some(s) => MetricType::from_string(s)
    None => None
  }
}

///|
/// 构建进度:已完成行数 / 总行数。总行数为 0 时返回 `None`——
/// 集合还空着,除法没有意义,也不该假装进度是 0%。
pub fn IndexDescription::progress(self : IndexDescription) -> Double? {
  if self.total_rows == 0L {
    return None
  }
  Some(self.indexed_rows.to_double() / self.total_rows.to_double())
}

///|
/// 在响应里按索引名挑一条。
///
/// 上游 `DescribeIndex` 用 `for` 循环找名字相等的一条,找不到就报 `IndexNotFound`;
/// 这里同样不取「第一条」充数——多索引集合下那是错的。
pub fn find_by_name(
  descriptions : Array[IndexDescription],
  index_name : String,
) -> IndexDescription? {
  for d in descriptions {
    if d.index_name == index_name {
      return Some(d)
    }
  }
  None
}

///|
/// 在响应里按字段名挑该字段上的全部索引。
pub fn find_by_field(
  descriptions : Array[IndexDescription],
  field_name : String,
) -> Array[IndexDescription] {
  let out : Array[IndexDescription] = []
  for d in descriptions {
    if d.field_name == field_name {
      out.push(d)
    }
  }
  out
}