/// engine_triage.mbt —— 下一步该做啥(Task Triage):给"更好的 AI 项目管理工具"补推荐式挑选原语
///
/// dag_ready/board 只"列出"可领取任务;本原语进一步"推荐排序"——对指定命名空间(缺省全量)
/// 把"待领取且依赖已满足"的任务按下述序排出优先级,并给出一条"建议下一步",供 agent
/// 无需全量扫描就能知道此刻该领哪条:
///   排序键:priority(高>中>低) → importance(高>中>低) → depth(叶=可执行单元优先,越小越前) → id(稳定)。
/// 难度标签直接复用 plan_deep gradient 嵌入子任务描述的 `难度梯度 i/n:易/中/难` 标记(未命中按深度粗判)。
/// 复用现有 store.list_all / deps_satisfied / get_status,无新表/依赖。

///|
/// 从任务描述提取 gradient 难度档(易/中/难),复用 R40 的 extract_difficulty 单一抽取来源:
/// 相比旧 `:易]` 后缀粗匹配,能正确识别 calibrate 的真实难度(如 `易 d=1.0` → 易)。
/// 未命中返回空串(由调用方按叶/分支兜底)。
fn triage_label_of(desc : String) -> String {
  let d = extract_difficulty(desc)
  if d.contains("难") {
    "难"
  } else if d.contains("中") {
    "中"
  } else if d.contains("易") {
    "易"
  } else {
    ""
  }
}

///|
/// 优先级/重要度的档位秩(越小越高优)。
fn ranking(x : String) -> Int {
  match x {
    "高" => 0
    "中" => 1
    "低" => 2
    _ => 1
  }
}

///|
/// 截断描述到 max 字符(避免返回里塞一整棵描述)。
fn truncate(s : String, max : Int) -> String {
  if s.length() <= max {
    s
  } else {
    s.substring(start=0, end=max) + "…"
  }
}

///|
/// 下一步排行行(triage 内部行)。
struct TriageRow {
  id : String
  ns : String
  priority : String
  importance : String
  difficulty : String
  depth : Int
  desc : String
  depends_on : Array[String]
}

///|
/// 候选描述/ id 是否命中能力关键词(want 为空视为全命中,即不做能力偏好)。
fn cap_match(r : TriageRow, want : String) -> Bool {
  want == "" || r.desc.contains(want) || r.id.contains(want)
}

///|
/// 下一步推荐(Triage,带能力偏好路由):返回 { count, ns, agent, want, suggestion?, tasks:[...] }。
/// namespace 为空表示全量;只统计"待领取且依赖已满足"的任务。want 为能力关键词(Marketplookup 雏形):
/// 非空时,命中该关键词的候选排在头部(按能力匹配路由"这个 agent 擅长啥就先领啥"),不命中者照旧跟进。
pub fn FistEngine::triage(
  self : FistEngine,
  namespace : String,
  agent : String,
  want : String,
) -> Json {
  let rows : Array[TriageRow] = []
  for t in self.list_all() {
    if namespace != "" && t.ns != namespace {
      continue
    }
    if not(t.get_status().is_pending()) {
      continue
    }
    if not(self.deps_satisfied(t.get_id())) {
      continue
    }
    let lab : String = triage_label_of(t.description)
    let difficulty = if lab != "" {
      lab
    } else if t.is_leaf() {
      "叶(可执行)"
    } else {
      "分支"
    }
    rows.push({
      id: t.get_id(),
      ns: t.ns,
      priority: t.priority,
      importance: t.importance,
      difficulty,
      depth: t.get_depth(),
      desc: truncate(t.description, 60),
      depends_on: t.depends_on,
    })
  }
  rows.sort_by(fn(a, b) {
    // 能力偏好:命中 want 的排在头部(Router 雏形)
    let am = cap_match(a, want)
    let bm = cap_match(b, want)
    if am != bm {
      if am {
        -1
      } else {
        1
      }
    } else {
      let pc = ranking(a.priority) - ranking(b.priority)
      if pc != 0 {
        pc
      } else {
        let ic = ranking(a.importance) - ranking(b.importance)
        if ic != 0 {
          ic
        } else if a.depth != b.depth {
          if a.depth < b.depth {
            -1
          } else {
            1
          }
        } else {
          a.id.compare(b.id)
        }
      }
    }
  })
  let tasks : Array[Json] = []
  for r in rows {
    tasks.push(
      Json::object({
        "id": Json::string(r.id),
        "ns": Json::string(r.ns),
        "priority": Json::string(r.priority),
        "importance": Json::string(r.importance),
        "difficulty": Json::string(r.difficulty),
        "depth": Json::number(r.depth.to_double()),
        "desc": Json::string(r.desc),
        // 真实 DAG 前驱(复用 R39 gradient_dag 建边)——"就绪原因"看得见
        "depends_on": Json::array(r.depends_on.map(fn(s) { Json::string(s) })),
      }),
    )
  }
  let m : Map[String, Json] = Map([
    ("count", Json::number(rows.length().to_double())),
    ("ns", Json::string(namespace)),
    ("agent", Json::string(agent)),
    ("want", Json::string(want)),
    ("tasks", Json::array(tasks)),
  ])
  if not(rows.is_empty()) {
    let top = rows[0]
    let cap = if cap_match(top, want) && want != "" {
      ";能力匹配<\{want}>"
    } else {
      ""
    }
    let reason = "依赖已满足;优先级\{top.priority}/重要度\{top.importance}/难度\{top.difficulty}\{cap},建议优先领取"
    m.set(
      "suggestion",
      Json::object({
        "task_id": Json::string(top.id),
        "ns": Json::string(top.ns),
        "reason": Json::string(reason),
      }),
    )
  }
  Json::object(m)
}

///|
/// 下一步自动取单(自驱推进):按 triage 的能力推荐取顶部候选并认领给 `agent`(0 状态迁移:待领取→已领取)。
/// 复用 `triage`(排行+suggestion)与 `claim`(认领),只动一条推荐任务,不动看门狗主流程(零回归)。
/// 返回 { picked, remaining, claimed, ns, want, task?, note|error? }。
pub fn FistEngine::pick_next(
  self : FistEngine,
  namespace : String,
  agent : String,
  want : String,
  now : String,
) -> Json {
  let tg = self.triage(namespace, agent, want)
  let m = match tg {
    Object(mm) => mm
    _ => Map([])
  }
  let count = match m.get("count") {
    Some(Number(n, ..)) => n.to_int()
    _ => 0
  }
  let m2 = match m.get("suggestion") {
    Some(Object(s)) => s
    _ => Map([])
  }
  let picked = match m2.get("task_id") {
    Some(String(id)) => Some(id)
    _ => None
  }
  match picked {
    None =>
      Json::object({
        "picked": Json::string(""),
        "remaining": Json::number(count.to_double()),
        "claimed": Json::boolean(false),
        "ns": Json::string(namespace),
        "want": Json::string(want),
        "note": Json::string(
          "无可领取的推荐任务(待领取且依赖已满足集合为空)",
        ),
      })
    Some(pid) =>
      match self.claim(task_id=pid, assignee=agent, now~) {
        Ok(t) =>
          Json::object({
            "picked": Json::string(pid),
            "remaining": Json::number(
              (if count > 0 { count - 1 } else { 0 }).to_double(),
            ),
            "claimed": Json::boolean(true),
            "ns": Json::string(namespace),
            "want": Json::string(want),
            "task": t.to_json(),
            "note": Json::string("已按能力推荐认领该条"),
          })
        Err(e) =>
          Json::object({
            "picked": Json::string(""),
            "remaining": Json::number(count.to_double()),
            "claimed": Json::boolean(false),
            "ns": Json::string(namespace),
            "want": Json::string(want),
            "error": Json::string(e),
            "note": Json::string("认领失败,未改动任何状态"),
          })
      }
  }
}