// ============================================================================
// BUG-470:认证模式开关(`FIST_AUTH_MODE`)—— **单一真源**
//
// owner 已批准「策略 B(默认降权)」。本文件提供三档开关,并把「默认值」
// 从「代码里的隐式行为」变成「一个显式的、可回滚的配置项」。
//
// ## 三档语义(**`off` 的定义容易误解,先读这段**)
//
// | 值 | 对「有公钥但不出示」 | 对「无公钥且不出示」 |
// |---|---|---|
// | `off`    | **降权**(S3 第九段,本开关之前就有)| **自报**(放行,原样)|
// | `soft`   | **降权** | **降权** ← **本开关新增的这层** |
// | `strict` | **拒绝** | **拒绝** |
//
// ⚠️ **`off` 不是「完全关闭认证」** —— S3 第九段的软强制降权(有钥匙不传 ⇒ 降权)
//   **在 `off` 下仍然生效**。`off` 关的只是「**对无钥匙者也降权**」这一层。
//   为什么这样定义:那一层是 BUG-430 第九段已经上线并锁住的行为,
//   把它放进开关里意味着「关开关 = 回退一个已交付的特性」—— 那不是开关,是倒车。
//   本开关的职责是「**是否把要求扩展到无钥匙者**」。
//
// ## 为什么读一次就缓存(不热切)
//
//   热切要处理「一个请求跨切换点」的语义漂移(这个请求算旧模式还是新模式?),
//   而重启成本只有几秒(`serve-launcher.cjs` 冷启 2-4 秒)。
//   **用简单换正确** —— 这也是决策单里写明的选择。
//
// ## 为什么默认 `off`
//
//   决策单:首次上线默认 `off`,观察一周后 flip 到 `soft`。
//   `off` 的行为必须与修前**逐字相同**,否则「上线开关」这件事本身就有回归风险。
// ============================================================================

///|
/// 认证模式(三档)。
///
/// `derive(Eq, Debug)`:让测试能 `assert_eq(auth_mode(), Off)` 并打印差值。
pub enum AuthMode {
  /// 只关「对无钥匙者也降权」这层;有钥匙不传仍降权(= 修前行为)
  Off
  /// 无钥匙也降权(owner 批准的策略 B)
  Soft
  /// 一律拒绝(未启用,前置条件见 BUG-470 决策单)
  Strict
} derive(Eq, Debug)

///|
/// **显式 extend(BUG-478 清理 warn)**:编译器 `implicit_impl_as_method` 要求把
/// `derive(Eq, Debug)` 生成的 impl 方法**显式声明**为普通方法,否则将被移除。
/// 不写这两行时 `AuthMode` 的 `.equal()` / `.to_string()`(Debug 路径)会在未来版本失效。
pub extend AuthMode with Eq::{not_equal, equal}

///|
pub extend AuthMode with @moonbitlang/core/debug.Debug::{to_repr}

///|
/// 转回 `FIST_AUTH_MODE` 的**取值字符串**(小写,与 env 里人配的字面量一致)。
///
/// ⚠️ 为什么不用 `Debug`:`Debug` 给的是 `Off`/`Soft`/`Strict`(大写),
///   与人配的 `off`/`soft`/`strict` **不一致** ⇒ 人读启动日志会以为配错了。
pub fn AuthMode::to_string(self : AuthMode) -> String {
  match self {
    Off => "off"
    Soft => "soft"
    Strict => "strict"
  }
}

///|
/// 解析 `FIST_AUTH_MODE` 的原始字符串(**纯函数,唯一判定面**)。
///
/// 取值(大小写不敏感、两端空白忽略):
///   · `"off"`    ⇒ `Off`
///   · `"soft"`   ⇒ `Soft`
///   · `"strict"` ⇒ `Strict`
///   · 空串 / 未设 ⇒ `Off`(默认值)
///   · **其它任何值** ⇒ `Off` + **warning**(见 `auth_mode_warning`)
///
/// ⚠️ **非法值回退 `Off` 而不是 `Strict`**:写错一个字母不该让现网停摆。
///   fail-closed 在这里是**错的方向** —— 因为本开关的「严」档会让 56 个调用方失败,
///   而「回退默认」只是维持现状。**配置写错 ≠ 环境不安全。**
pub fn auth_mode_parse(raw : String) -> AuthMode {
  match normalize_mode_str(raw) {
    "" => Off
    "off" => Off
    "soft" => Soft
    "strict" => Strict
    // 非法值 ⇒ 回退默认(不 panic、不取最严)
    _ => Off
  }
}

///|
/// 非法值时的 warning 文案;合法值 / 未设时返回空串(调用方据此决定是否打日志)。
///
/// 为什么要单独一个函数:`auth_mode_parse` 必须保持**纯**(可测、无副作用),
/// 而「要不要警告」是另一个判断(判例-29:判定面与渲染面分开)。
pub fn auth_mode_warning(raw : String) -> String {
  let n = normalize_mode_str(raw)
  if n == "" || n == "off" || n == "soft" || n == "strict" {
    ""
  } else {
    "FIST_AUTH_MODE=\"" +
    raw +
    "\" 不是合法值(期望 off / soft / strict)⇒ **已回退 off(维持现状)**。" +
    "配置写错不会让现网停摆,但请修正,否则你以为开了 B 其实没开。"
  }
}

///|
/// 规范化:去两端空白 + 转小写(`FIST_AUTH_MODE=OFF` 与 `off` 等价)。
fn normalize_mode_str(raw : String) -> String {
  // `trim()` 返回 StringView ⇒ 先转 String 再 to_lower()
  let t : String = raw.trim().to_owned()
  t.to_lower()
}

///|
/// 进程级缓存(首次读取后不再读 env —— 见文件头「为什么不做热切」)。
///
/// ⚠️ MoonBit **不支持顶层 `let mut`**(实测 `Error [4074]`)⇒ 用一个单元素数组当可变槽。
/// 这是本仓已有的同族手法(`s3_pending_nonces` / `s3_challenge_counter` 都是顶层可变容器)。
let auth_mode_slot : Array[String] = [""]

///|
/// **唯一**的 env 读取点(单一真源,判例-29)。
///
/// 首次调用读 `FIST_AUTH_MODE` 并缓存;之后直接返回缓存。
/// 任何其它地方要问「当前是什么模式」都必须调 `auth_mode()`,**不得直接读 env**。
pub fn auth_mode() -> AuthMode {
  if auth_mode_slot[0] == "" {
    let raw = match @env.get_env_var("FIST_AUTH_MODE") {
      Some(v) => v
      None => ""
    }
    // 缓存**规范化后的合法值**(非法值也缓存为 "off",避免每次调用都重复判定)
    let n = normalize_mode_str(raw)
    auth_mode_slot[0] = if n == "off" || n == "soft" || n == "strict" {
      n
    } else {
      "off"
    }
  }
  auth_mode_parse(auth_mode_slot[0])
}

///|
/// 测试专用:重置缓存(让同一进程内能切换模式跑多格测试)。
///
/// ⚠️ 生产代码**不要调** —— 它会让「读一次就定」的保证失效。
pub fn auth_mode_reset_cache_for_test() -> Unit {
  auth_mode_slot[0] = ""
}

///|
/// 仅在**进程启动时调一次**,拿到「配置有问题」的 warning(无问题则空串)。
///
/// 两类问题都返回非空:
///   1. **非法值**(如 `garbage`)⇒ 已回退 `off`
///   2. **`strict` 尚未真正生效**(见下)
///
/// ⚠️ 为什么 `strict` 也要告警:本版只做到「返回专用 `challenge_missing` 标签 + 降权」,
///   **各门的消费者还没据此硬拒** ⇒ 实际效果接近 `soft`。
///   若有人设 `strict` 以为「现在严格了」,那是**比不设更危险**的状态(以为自己安全了)。
pub fn auth_mode_startup_warning() -> String {
  let raw = match @env.get_env_var("FIST_AUTH_MODE") {
    Some(v) => v
    None => ""
  }
  let w = auth_mode_warning(raw)
  if w != "" {
    return w
  }
  if normalize_mode_str(raw) == "strict" {
    return "FIST_AUTH_MODE=strict 已设置,但**本版 strict 尚未真正拒绝**:" +
      "目前只把 role_source 标成 challenge_missing 并降权到 Executor," +
      "各门的消费者(rsv_set / rsv_try_set / locks_try_acquire / claim)还没据此硬拒 ⇒ " +
      "**实际效果接近 soft**。不要以为已经严格了(见 BUG-470 R1)。"
  }
  ""
}