///|
/// add(对齐上游 add.rs):
/// - 按 _JB_RESOLVE_SYMLINKS 选择 realpath(canonicalize)或 resolve_path
/// - 换行路径 / _JB_EXCLUDE_DIRS glob 命中 → 静默跳过;非目录 → 立即报错中止
/// - 全部 add_update(score) 后,若 dirty 则 age(_JB_MAXAGE) 再 save
async fn run_add(
  aopts : @cli.AddOptions,
  getenv : (String) -> String?,
  errs : Array[String],
) -> Int {
  if aopts.paths.is_empty() {
    err_line(errs, "add: missing required argument ''")
    return 2
  }
  let data_dir = match cfg(@config.data_dir(getenv), errs) {
    Some(data_dir) => data_dir
    None => return 1
  }
  let globs = match cfg(@config.exclude_dirs(getenv, getenv("HOME")), errs) {
    Some(globs) => globs
    None => return 1
  }
  let maxage = match cfg(@config.maxage(getenv), errs) {
    Some(maxage) => maxage
    None => return 1
  }
  let resolve_symlinks = @config.resolve_symlinks(getenv)
  let store = match open_store(data_dir, errs) {
    Some(s) => s
    None => return 1
  }
  let cwd = match @env.current_dir() {
    Some(d) => d
    None => "/"
  }
  let now = current_second()
  for path in aopts.paths {
    let resolved = @core.resolve_path(path, cwd)
    let resolved = if resolve_symlinks {
      @afs.realpath(resolved) catch {
        err => {
          // os_error_message 自带 (os error N) 后缀,用冒号拼接避免括号嵌套
          err_line(
            errs,
            "jumbit: could not resolve path \{resolved}: \{@platform.os_error_message(err)}",
          )
          return 1
        }
      }
    } else {
      resolved
    }
    // 换行/回车无法整洁地打印在单行,静默跳过(对齐上游 EXCLUDE_CHARS)
    if resolved.find("\n") is Some(_) || resolved.find("\r") is Some(_) {
      continue
    }
    // _JB_EXCLUDE_DIRS 命中 → 静默跳过
    if glob_hit(globs, resolved) {
      continue
    }
    let kind = @afs.kind(resolved) catch {
      err => {
        ignore(err)
        @afs.Unknown
      }
    }
    if kind != @afs.Directory {
      err_line(errs, "not a directory: \{resolved}")
      return 1
    }
    store.database().add_update(resolved, aopts.score, now)
    if echo_enabled(getenv) {
      println(resolved)
    }
  }
  if store.database().is_dirty() {
    store.database().age(maxage)
  }
  finish(store, errs, 0)
}

///|
/// remove(对齐上游 remove.rs):先按原始串删,未中再按规范化路径删,仍未中报错
async fn run_remove(
  paths : Array[String],
  getenv : (String) -> String?,
  errs : Array[String],
) -> Int {
  if paths.is_empty() {
    err_line(errs, "remove: missing required argument ''")
    return 2
  }
  let data_dir = match cfg(@config.data_dir(getenv), errs) {
    Some(data_dir) => data_dir
    None => return 1
  }
  let store = match open_store(data_dir, errs) {
    Some(s) => s
    None => return 1
  }
  let cwd = match @env.current_dir() {
    Some(d) => d
    None => "/"
  }
  for path in paths {
    let removed = try_twice(store.database(), path, cwd, fn(db, p) {
      db.remove(p)
    })
    if !removed {
      err_line(errs, "path not found in database: \{path}")
      return 1
    }
  }
  finish(store, errs, 0)
}