///|
/// 变体探索:Agent 并行尝试多个设计方案,评审后选最优。
///
/// 设计师的工作流是"做几版然后挑最好的"。
/// Agent 的等价体验:`fork_variants(project, base, ["v1","v2","v3"])`
/// → 引擎为每个变体创建独立的画板分支
/// → Agent 分别修改每个变体
/// → `score_variants(project)` 对所有变体跑 lint + 谓词 + 对齐建议
/// → Agent 据此选出最优方案并合并
pub(all) struct VariantScore {
  artboard : String
  violations : Int // 不崩谓词违规数
  lint_issues : Int // 设计 lint 问题数
  alignment_hints : Int // 对齐建议数
  node_count : Int
  score : Int // 综合评分(越高越好)
}

///|
/// 从基础画板 fork 出多个变体
pub fn Project::fork_variants(
  self : Project,
  base~ : String,
  names~ : Array[String],
) -> Result[String, String] {
  match self.artboards.get(base) {
    None => Err("unknown_artboard: \{base}")
    Some(base_ab) => {
      let created : Array[String] = []
      for name in names.iter() {
        let id = self.create_artboard(
          name~,
          width=base_ab.width,
          height=base_ab.height,
        )
        // 复制所有节点
        match self.artboards.get(id) {
          Some(new_ab) =>
            for cid in base_ab.doc.nodes.keys() {
              if cid != "root" {
                match base_ab.doc.nodes.get(cid) {
                  Some(n) => {
                    let _ = new_ab.doc.insert_node(
                      { ..n, children: [], },
                      "root",
                    )
                    // 重建子关系
                    match base_ab.doc.nodes.get(cid) {
                      Some(src) => {
                        let _ = new_ab.doc.update_node(cid, fn(x) {
                          { ..x, children: src.children.copy(), }
                        })
                      }
                      None => ()
                    }
                  }
                  None => ()
                }
              }
            }
          None => ()
        }
        created.push("\"\{id}\"")
      }
      let j = jstr11(created)
      Ok("{\"ok\":true,\"base\":\"\{base}\",\"variants\":[\{j}]}")
    }
  }
}

///|
/// 对所有画板评分( violations 越少越好)
pub fn Project::score_variants(self : Project) -> String {
  let scores : Array[String] = []
  for id in self.artboards.keys() {
    match self.artboards.get(id) {
      Some(ab) => {
        let violations = validate(ab.doc).length()
        let lint_count = match self.lint(artboard=id) {
          Ok(json) => count_occurrences(json, "\"severity\"")
          Err(_) => 0
        }
        let align_count = match self.suggest_alignment(artboard=id) {
          Ok(json) => count_occurrences(json, "\"type\"")
          Err(_) => 0
        }
        let nodes = ab.doc.nodes.size() - 1 // 减 root
        // 评分:节点多(丰富)+ 违规少 + lint 少 = 高分
        let score = nodes * 10 -
          violations * 50 -
          lint_count * 10 -
          align_count * 5
        scores.push(
          "{\"artboard\":\"\{id}\",\"violations\":\{violations},\"lint\":\{lint_count},\"alignment_hints\":\{align_count},\"nodes\":\{nodes},\"score\":\{score}}",
        )
      }
      None => ()
    }
  }
  let j = jstr11(scores)
  "[\{j}]"
}

///|
/// 合并变体:把源画板的内容替换到目标画板
pub fn Project::merge_variant(
  self : Project,
  source~ : String,
  target~ : String,
) -> Result[String, String] {
  match (self.artboards.get(source), self.artboards.get(target)) {
    (Some(src), Some(tgt)) => {
      // 清空目标
      tgt.doc.nodes = Map::new()
      tgt.doc.root_id = "root"
      // 复制源
      let root_node = match src.doc.nodes.get(src.doc.root_id) {
        Some(r) => { ..r, children: r.children.copy(), }
        None => Node::make("root", Frame)
      }
      tgt.doc.content_seq = tgt.doc.content_seq + 1
      tgt.doc.nodes.set("root", root_node)
      for id in src.doc.nodes.keys() {
        if id != "root" {
          match src.doc.nodes.get(id) {
            Some(n) =>
              tgt.doc.nodes.set(id, { ..n, children: n.children.copy(), })
            None => ()
          }
        }
      }
      self.revision = self.revision + 1
      Ok("{\"ok\":true,\"merged\":\"\{source}\" → \"\{target}\"}")
    }
    _ => Err("unknown_artboard")
  }
}

///|
fn count_occurrences(s : String, sub : String) -> Int {
  let mut count = 0
  let mut pos = 0
  let sub_len = sub.length()
  while pos < s.length() {
    match s.substring(start=pos).find(sub) {
      Some(idx) => {
        count = count + 1
        pos = pos + idx + sub_len
      }
      None => break
    }
  }
  count
}

///|
fn jstr11(parts : Array[String]) -> String {
  let sb = StringBuilder::new()
  for i in 0.. 0 {
      sb.write_string(",")
    }
    sb.write_string(parts[i])
  }
  sb.to_string()
}