// 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/{maintenance.go,
// maintenance_options.go} 的 load / release 部分与 client/entity/load_state.go
// (Apache-2.0)。
//
// 上游 `LoadCollection` / `LoadPartitions` 返回一个 `LoadTask`,`Await` 里
// 每 200ms 轮询 `GetLoadingProgress` 直到 `progress == 100`。本移植照搬,
// 只是把等待交给 `@async.sleep`,并把轮询间隔做成可配的(默认 200ms 与上游一致)。
//
// 上游还区分「refresh 模式」:`refresh = true` 时等的是 `refresh_progress`
// 而不是 `progress`。本移植保留这个开关,语义一致。

///|
/// 集合 / 分区的加载状态。对应上游 `entity.LoadState`。
pub(all) struct LoadState {
  /// 加载档位。
  state : CollectionLoadState
  /// 已加载百分比(0–100)。只在 `Loading` 时有意义。
  progress : Int64
} derive(Eq, Debug)

///|
/// 加载档位。序号与 `common.LoadState` 对齐,`to_int` / `from_int`
/// 是 wire 上的枚举值,不是客户端概念。
pub(all) enum CollectionLoadState {
  /// 集合/分区不存在。
  NotExist
  /// 存在但没加载。
  NotLoad
  /// 正在加载。
  Loading
  /// 已加载。
  Loaded
  /// 上游新增而本客户端尚未认识的值。保留原始序号,不猜测语义。
  Unknown(Int)
} derive(Eq, Debug)

///|
pub fn CollectionLoadState::to_int(self : CollectionLoadState) -> Int {
  match self {
    NotExist => 0
    NotLoad => 1
    Loading => 2
    Loaded => 3
    Unknown(n) => n
  }
}

///|
pub fn CollectionLoadState::from_int(n : Int) -> CollectionLoadState {
  match n {
    0 => NotExist
    1 => NotLoad
    2 => Loading
    3 => Loaded
    _ => Unknown(n)
  }
}

///|
/// 从生成的 proto 枚举翻过来。
pub fn CollectionLoadState::from_proto(
  state : @common.LoadState,
) -> CollectionLoadState {
  match state {
    @common.LoadState::LoadStateNotExist => NotExist
    @common.LoadState::LoadStateNotLoad => NotLoad
    @common.LoadState::LoadStateLoading => Loading
    @common.LoadState::LoadStateLoaded => Loaded
    // 生成物是 open enum,多出来的分支按原始序号收,不假装认识。
    @common.LoadState::Unknown(code) => Unknown(code.0.reinterpret_as_int())
  }
}

///|
/// 加载完成的判据:`Loaded` 即成功,其余都还没到。
pub fn CollectionLoadState::is_loaded(self : CollectionLoadState) -> Bool {
  match self {
    Loaded => true
    _ => false
  }
}

///|
/// 上游 `NewLoadCollectionOption` 的默认轮询间隔。
pub let default_load_check_interval_millis : Int = 200

///|
/// 加载集合的入参。对应上游 `loadCollectionOption`。
pub(all) struct LoadCollectionOption {
  collection_name : String
  /// 副本数。0 表示走服务端默认(上游注释也是这个意思,默认值在服务端)。
  replica_number : Int
  resource_groups : Array[String]
  /// 字段级部分加载:只加载这些字段。空数组表示全字段。
  load_fields : Array[String]
  /// 字段级部分加载时跳过动态字段。
  skip_load_dynamic_field : Bool
  /// 透传给服务端的额外加载参数。上游也是这个出口。
  load_params : Array[(String, String)]
  /// refresh 模式:等 `refresh_progress` 而不是 `progress`。
  refresh : Bool
  /// `wait` 的轮询间隔。
  check_interval_millis : Int
}

///|
pub fn new_load_collection_option(
  collection_name : String,
) -> LoadCollectionOption {
  {
    collection_name,
    replica_number: 0,
    resource_groups: [],
    load_fields: [],
    skip_load_dynamic_field: false,
    load_params: [],
    refresh: false,
    check_interval_millis: default_load_check_interval_millis,
  }
}

///|
pub fn LoadCollectionOption::with_replica(
  self : LoadCollectionOption,
  replica_number : Int,
) -> LoadCollectionOption {
  { ..self, replica_number, }
}

///|
/// `with_replica` 的别名。上游 Go 侧叫 `WithReplica`,
/// 早先的集成自检按 `with_replica_number` 写,两个都留着少一次改名。
pub fn LoadCollectionOption::with_replica_number(
  self : LoadCollectionOption,
  replica_number : Int,
) -> LoadCollectionOption {
  { ..self, replica_number, }
}

///|
/// 追加一条加载参数。同名键覆盖已有的。
pub fn LoadCollectionOption::with_load_param(
  self : LoadCollectionOption,
  key : String,
  value : String,
) -> LoadCollectionOption {
  { ..self, load_params: upsert_pair(self.load_params, key, value), }
}

///|
/// 键值对数组的 upsert:同名键替换,其余保持原序。
fn upsert_pair(
  pairs : Array[(String, String)],
  key : String,
  value : String,
) -> Array[(String, String)] {
  let out : Array[(String, String)] = []
  let mut replaced = false
  for pair in pairs {
    if pair.0 == key {
      out.push((key, value))
      replaced = true
    } else {
      out.push(pair)
    }
  }
  if !replaced {
    out.push((key, value))
  }
  out
}

///|
pub fn LoadCollectionOption::with_resource_groups(
  self : LoadCollectionOption,
  resource_groups : Array[String],
) -> LoadCollectionOption {
  { ..self, resource_groups, }
}

///|
pub fn LoadCollectionOption::with_load_fields(
  self : LoadCollectionOption,
  load_fields : Array[String],
) -> LoadCollectionOption {
  { ..self, load_fields, }
}

///|
pub fn LoadCollectionOption::with_skip_load_dynamic_field(
  self : LoadCollectionOption,
  skip? : Bool = true,
) -> LoadCollectionOption {
  { ..self, skip_load_dynamic_field: skip, }
}

///|
pub fn LoadCollectionOption::with_refresh(
  self : LoadCollectionOption,
  refresh? : Bool = true,
) -> LoadCollectionOption {
  { ..self, refresh, }
}

///|
pub fn LoadCollectionOption::with_check_interval_millis(
  self : LoadCollectionOption,
  millis : Int,
) -> LoadCollectionOption {
  { ..self, check_interval_millis: millis, }
}

///|
/// 加载任务。上游 `LoadTask`:拿着集合名与轮询间隔,`Await` 到加载完成。
pub struct LoadTask {
  client : Client
  collection_name : String
  /// 空数组表示「整个集合」。非空表示「只等这些分区」。
  partition_names : Array[String]
  interval_millis : Int
  refresh : Bool
}

///|
pub fn LoadTask::collection_name(self : LoadTask) -> String {
  self.collection_name
}

///|
pub fn LoadTask::partition_names(self : LoadTask) -> Array[String] {
  self.partition_names
}

///|
/// 查一次加载进度(0–100)。不等待。
pub async fn LoadTask::progress(self : LoadTask) -> Int64 raise ClientError {
  let request = @milvus.GetLoadingProgressRequest::{
    base: None,
    collection_name: self.collection_name,
    partition_names: self.partition_names,
    db_name: "",
  }
  let response : @milvus.GetLoadingProgressResponse = self.client.call_service(
    get_loading_progress_path, request,
  )
  match check_status(response.status) {
    Some(err) => raise err
    None =>
      if self.refresh {
        response.refresh_progress
      } else {
        response.progress
      }
  }
}

///|
/// 这块数据现在能不能查。`refresh` 模式下看的是 `refresh_progress`。
pub async fn LoadTask::is_loaded(self : LoadTask) -> Bool raise ClientError {
  self.progress() == 100L
}

///|
/// 轮询到加载完成。间隔取 `check_interval_millis`,与上游 `LoadTask.Await`
/// 一致:先等一个间隔再查第一次。
///
/// 上游靠 `ctx.Done()` 退出;本移植里取消是 `@async.sleep` 抛出的,
/// 按 `Code::Cancelled` 归到 `Transport` 一类,调用方仍可用
/// `@async.with_timeout` 之类的外层机制收口。
pub async fn LoadTask::wait(self : LoadTask) -> Unit raise ClientError {
  while !self.is_loaded() {
    sleep_or_cancel(self.interval_millis)
  }
}

///|
/// `@async.sleep` 失败只有取消一种可能,翻成 `Code::Cancelled` 的传输错误,
/// 好让 `ClientError::is_transport` 能把它认出来。
async fn sleep_or_cancel(millis : Int) -> Unit raise ClientError {
  @async.sleep(millis) catch {
    err =>
      raise ClientError::Transport(
        @transport.RpcError::new(
          @transport.Code::Cancelled.to_int(),
          "等待被取消: " + err.to_string(),
        ),
      )
  }
}

///|
/// 加载集合。返回的 `LoadTask` 可以用 `wait` 等到加载完成,
/// 也可以拿 `progress` 自己轮询。
pub async fn Client::load_collection(
  self : Client,
  option : LoadCollectionOption,
) -> LoadTask raise ClientError {
  let request = @milvus.LoadCollectionRequest::{
    base: None,
    db_name: "",
    collection_name: option.collection_name,
    replica_number: option.replica_number,
    resource_groups: option.resource_groups,
    refresh: option.refresh,
    load_fields: option.load_fields,
    skip_load_dynamic_field: option.skip_load_dynamic_field,
    load_params: Map::from_array(option.load_params),
  }
  let response : @common.Status = self.call_service(
    load_collection_path, request,
  )
  match check_status(Some(response)) {
    Some(err) => raise err
    None =>
      {
        client: self,
        collection_name: option.collection_name,
        partition_names: [],
        interval_millis: option.check_interval_millis,
        refresh: option.refresh,
      }
  }
}

///|
/// 卸载集合的入参。
pub(all) struct ReleaseCollectionOption {
  collection_name : String
}

///|
pub fn new_release_collection_option(
  collection_name : String,
) -> ReleaseCollectionOption {
  { collection_name, }
}

///|
/// 把集合从查询节点卸载。卸载后检索会报错,这是判据。
pub async fn Client::release_collection(
  self : Client,
  option : ReleaseCollectionOption,
) -> Unit raise ClientError {
  let request = @milvus.ReleaseCollectionRequest::{
    base: None,
    db_name: "",
    collection_name: option.collection_name,
  }
  let response : @common.Status = self.call_service(
    release_collection_path, request,
  )
  match check_status(Some(response)) {
    Some(err) => raise err
    None => ()
  }
}

///|
/// 加载分区的入参。字段与 `LoadCollectionOption` 一一对应,只是多了分区名。
pub(all) struct LoadPartitionsOption {
  collection_name : String
  partition_names : Array[String]
  replica_number : Int
  resource_groups : Array[String]
  load_fields : Array[String]
  skip_load_dynamic_field : Bool
  load_params : Array[(String, String)]
  refresh : Bool
  check_interval_millis : Int
}

///|
pub fn new_load_partitions_option(
  collection_name : String,
  partition_names : Array[String],
) -> LoadPartitionsOption {
  {
    collection_name,
    partition_names,
    replica_number: 0,
    resource_groups: [],
    load_fields: [],
    skip_load_dynamic_field: false,
    load_params: [],
    refresh: false,
    check_interval_millis: default_load_check_interval_millis,
  }
}

///|
pub fn LoadPartitionsOption::with_replica(
  self : LoadPartitionsOption,
  replica_number : Int,
) -> LoadPartitionsOption {
  { ..self, replica_number, }
}

///|
pub fn LoadPartitionsOption::with_resource_groups(
  self : LoadPartitionsOption,
  resource_groups : Array[String],
) -> LoadPartitionsOption {
  { ..self, resource_groups, }
}

///|
pub fn LoadPartitionsOption::with_load_fields(
  self : LoadPartitionsOption,
  load_fields : Array[String],
) -> LoadPartitionsOption {
  { ..self, load_fields, }
}

///|
pub fn LoadPartitionsOption::with_skip_load_dynamic_field(
  self : LoadPartitionsOption,
  skip? : Bool = true,
) -> LoadPartitionsOption {
  { ..self, skip_load_dynamic_field: skip, }
}

///|
/// `with_replica` 的别名,理由同 `LoadCollectionOption::with_replica_number`。
pub fn LoadPartitionsOption::with_replica_number(
  self : LoadPartitionsOption,
  replica_number : Int,
) -> LoadPartitionsOption {
  { ..self, replica_number, }
}

///|
/// 追加一条加载参数。同名键覆盖已有的。
pub fn LoadPartitionsOption::with_load_param(
  self : LoadPartitionsOption,
  key : String,
  value : String,
) -> LoadPartitionsOption {
  { ..self, load_params: upsert_pair(self.load_params, key, value), }
}

///|
pub fn LoadPartitionsOption::with_refresh(
  self : LoadPartitionsOption,
  refresh? : Bool = true,
) -> LoadPartitionsOption {
  { ..self, refresh, }
}

///|
pub fn LoadPartitionsOption::with_check_interval_millis(
  self : LoadPartitionsOption,
  millis : Int,
) -> LoadPartitionsOption {
  { ..self, check_interval_millis: millis, }
}

///|
/// 加载分区。返回的 `LoadTask` 只等这些分区。
pub async fn Client::load_partitions(
  self : Client,
  option : LoadPartitionsOption,
) -> LoadTask raise ClientError {
  let request = @milvus.LoadPartitionsRequest::{
    base: None,
    db_name: "",
    collection_name: option.collection_name,
    partition_names: option.partition_names,
    replica_number: option.replica_number,
    resource_groups: option.resource_groups,
    refresh: option.refresh,
    load_fields: option.load_fields,
    skip_load_dynamic_field: option.skip_load_dynamic_field,
    load_params: Map::from_array(option.load_params),
  }
  let response : @common.Status = self.call_service(
    load_partitions_path, request,
  )
  match check_status(Some(response)) {
    Some(err) => raise err
    None =>
      {
        client: self,
        collection_name: option.collection_name,
        partition_names: option.partition_names,
        interval_millis: option.check_interval_millis,
        refresh: option.refresh,
      }
  }
}

///|
/// 卸载分区的入参。
pub(all) struct ReleasePartitionsOption {
  collection_name : String
  partition_names : Array[String]
}

///|
pub fn new_release_partitions_option(
  collection_name : String,
  partition_names : Array[String],
) -> ReleasePartitionsOption {
  { collection_name, partition_names, }
}

///|
/// 卸载分区。
pub async fn Client::release_partitions(
  self : Client,
  option : ReleasePartitionsOption,
) -> Unit raise ClientError {
  let request = @milvus.ReleasePartitionsRequest::{
    base: None,
    db_name: "",
    collection_name: option.collection_name,
    partition_names: option.partition_names,
  }
  let response : @common.Status = self.call_service(
    release_partitions_path, request,
  )
  match check_status(Some(response)) {
    Some(err) => raise err
    None => ()
  }
}

///|
/// 查加载状态的入参。
pub(all) struct GetLoadStateOption {
  collection_name : String
  partition_names : Array[String]
}

///|
pub fn new_get_load_state_option(
  collection_name : String,
  partition_names? : Array[String] = [],
) -> GetLoadStateOption {
  { collection_name, partition_names, }
}

///|
/// 查集合 / 分区的加载状态。
///
/// 与上游一致:状态是 `Loading` 时顺带查一次进度,这样调用方拿到的
/// `progress` 在加载中才有意义;其余状态进度留 0。
pub async fn Client::get_load_state(
  self : Client,
  option : GetLoadStateOption,
) -> LoadState raise ClientError {
  let request = @milvus.GetLoadStateRequest::{
    base: None,
    collection_name: option.collection_name,
    partition_names: option.partition_names,
    db_name: "",
  }
  let response : @milvus.GetLoadStateResponse = self.call_service(
    get_load_state_path, request,
  )
  match check_status(response.status) {
    Some(err) => raise err
    None => {
      let state = CollectionLoadState::from_proto(response.state)
      let progress = if state is Loading {
        let progress_request = @milvus.GetLoadingProgressRequest::{
          base: None,
          collection_name: option.collection_name,
          partition_names: option.partition_names,
          db_name: "",
        }
        let progress_response : @milvus.GetLoadingProgressResponse = self.call_service(
          get_loading_progress_path, progress_request,
        )
        match check_status(progress_response.status) {
          Some(err) => raise err
          None => progress_response.progress
        }
      } else {
        0L
      }
      { state, progress, }
    }
  }
}