///|
/// The `mb env` command group: manage dotenv files for the current project and
/// for the user as a whole.
///
/// - project scope (default): `./.env` (and `./.mbenv` when present);
/// - global scope (`--global`): `$HOME/.mbenv`, which also holds the key.
///
/// Values are encrypted individually, so listing a file never needs the key.

///|
/// Resolve the encryption key, prompting for one the first time it is needed.
fn env_require_key() -> String raise MbError {
  match read_env_key() {
    Some(k) => k
    None => {
      let path = global_env_file()
      println("No encryption key found.")
      println("Enter a key to encrypt env values (stored in " + path + "):")
      let key = @utf8.decode_lossy(mb_read_line()).trim().to_owned()
      if key == "" {
        raise MbError("no key provided")
      }
      let (_, entries) = read_global_env()
      write_global_env(Some(key), entries)
      println("Stored key in " + path)
      key
    }
  }
}

///|
fn env_upsert(
  entries : Array[(String, String)],
  key : String,
  value : String,
) -> Array[(String, String)] {
  let out : Array[(String, String)] = []
  let mut replaced = false
  for entry in entries {
    let (k, v) = entry
    if k == key {
      out.push((k, value))
      replaced = true
    } else {
      out.push((k, v))
    }
  }
  if !replaced {
    out.push((key, value))
  }
  out
}

///|
/// The `mb env set` command. Encrypts the value unless `plain` is set.
fn env_set(
  key : String,
  value : String,
  global : Bool,
  plain : Bool,
) -> Unit raise MbError {
  let stored = if plain {
    value
  } else {
    encrypt_value(value, env_require_key())
  }
  if global {
    let (k, entries) = read_global_env()
    write_global_env(k, env_upsert(entries, key, stored))
    println(key + " set in " + global_env_file())
  } else {
    write_env_file(".env", env_upsert(read_env_file(".env"), key, stored))
    println(key + " set in .env")
  }
}

///|
/// The `mb env rm` command: remove one or more keys.
fn env_rm(keys : Array[String], global : Bool) -> Unit raise MbError {
  let path = if global { global_env_file() } else { ".env" }
  let (existing_key, entries) = if global {
    read_global_env()
  } else {
    (None, read_env_file(path))
  }
  let out : Array[(String, String)] = []
  let mut removed = 0
  for entry in entries {
    let (k, v) = entry
    let mut drop = false
    for want in keys {
      if want == k {
        drop = true
      }
    }
    if drop {
      removed = removed + 1
    } else {
      out.push((k, v))
    }
  }
  if removed == 0 {
    raise MbError("no such key in " + path + ": " + keys.join(", "))
  }
  if global {
    write_global_env(existing_key, out)
  } else {
    write_env_file(path, out)
  }
  println("Removed " + removed.to_string() + " key(s) from " + path)
}

///|
/// The `mb env list` command.
///
/// Values are only revealed when stdout is a terminal, so a captured or piped
/// listing shows encrypted values as `` instead of their contents.
/// `mask` forces masking even on a terminal.
fn env_list(global : Bool, mask : Bool) -> Unit raise MbError {
  let path = if global { global_env_file() } else { ".env" }
  let reveal = !mask && is_tty()
  let entries = if reveal {
    load_env_file(path)
  } else if global {
    let (_, e) = read_global_env()
    e
  } else {
    read_env_file(path)
  }
  if entries.is_empty() {
    println("No env entries in " + path + ".")
    return
  }
  println("Env entries in " + path + ":")
  for entry in entries {
    let (k, v) = entry
    if is_encrypted_value(v) {
      println("  " + k + "=")
    } else {
      println("  " + k + "=" + v)
    }
  }
}