/// 组件血缘追踪:改了一个令牌,哪些节点会受影响?
///
/// 这是"设计系统治理"的核心能力:
/// - token_lineage("primary") → 所有使用 primary 色的节点
/// - component_lineage("button") → 所有 button 实例所在的画板
/// - impact_analysis(NodePatch) → 如果应用这个补丁,会改什么
///
/// Agent 在修改前调用 impact_analysis 预览影响范围。

// ---------------------------------------------------------------------------
// 令牌血缘
// ---------------------------------------------------------------------------

///|
/// 查找使用指定颜色令牌的所有节点
pub fn Project::token_lineage(
  self : Project,
  token_name~ : String,
) -> Result[String, String] {
  let hex = self.tokens.resolve_color(token_name)
  let usages : Array[String] = []
  for ab_id in self.artboards.keys() {
    match self.artboards.get(ab_id) {
      Some(ab) =>
        for node_id in ab.doc.nodes.keys() {
          match ab.doc.nodes.get(node_id) {
            Some(n) =>
              if n.style.fill == hex ||
                n.style.text_color == hex ||
                n.style.stroke == hex {
                usages.push(
                  "{\"artboard\":\"\{ab_id}\",\"node\":\"\{node_id}\",\"component\":\"\{n.component}\",\"field\":\"\{style_field(n, hex)}\"}",
                )
              }
            None => ()
          }
        }
      None => ()
    }
  }
  let j = jstr15(usages)
  Ok(
    "{\"token\":\"\{token_name}\",\"hex\":\"\{hex}\",\"usage_count\":\{usages.length()},\"usages\":[\{j}]}",
  )
}

///|
fn style_field(n : Node, hex : String) -> String {
  if n.style.fill == hex {
    "fill"
  } else if n.style.text_color == hex {
    "text_color"
  } else {
    "stroke"
  }
}

// ---------------------------------------------------------------------------
// 组件血缘
// ---------------------------------------------------------------------------

///|
/// 查找指定组件类型的所有实例
pub fn Project::component_lineage(
  self : Project,
  component_id~ : String,
) -> Result[String, String] {
  let instances : Array[String] = []
  for ab_id in self.artboards.keys() {
    match self.artboards.get(ab_id) {
      Some(ab) => {
        // 每画板解一次布局复用——曾把 solve 放在逐节点循环体内,每命中一个
        // 实例就全量重解布局(O(n²),50 个实例 = 50 次完整约束求解)
        let layout = solve(ab.doc)
        for node_id in ab.doc.nodes.keys() {
          match ab.doc.nodes.get(node_id) {
            Some(n) =>
              if n.component == component_id {
                let rect_str = match layout.rect_of(node_id) {
                  Some(r) =>
                    "(\{r.x.to_int()},\{r.y.to_int()}) \{r.width.to_int()}×\{r.height.to_int()}"
                  None => "unsolved"
                }
                instances.push(
                  "{\"artboard\":\"\{ab_id}\",\"node\":\"\{node_id}\",\"name\":\"\{n.name}\",\"rect\":\"\{rect_str}\"}",
                )
              }
            None => ()
          }
        }
      }
      None => ()
    }
  }
  let j = jstr15(instances)
  Ok(
    "{\"component\":\"\{component_id}\",\"instance_count\":\{instances.length()},\"instances\":[\{j}]}",
  )
}

// ---------------------------------------------------------------------------
// 变更影响分析
// ---------------------------------------------------------------------------

///|
/// 预览:如果对某个节点应用补丁,会产生什么影响
pub fn Project::impact_analysis(
  self : Project,
  artboard~ : String,
  node_id~ : String,
  patch~ : NodePatch,
) -> Result[String, String] {
  match self.artboards.get(artboard) {
    None => Err("unknown_artboard")
    Some(ab) =>
      match ab.doc.nodes.get(node_id) {
        None => Err("unknown_node: \{node_id}")
        Some(n) => {
          let impacts : Array[String] = []
          // 颜色变更影响
          match patch.fill {
            Some(new_fill) => {
              let resolved = self.tokens.resolve_color(new_fill)
              if n.style.fill != resolved {
                impacts.push(
                  "{\"type\":\"style\",\"field\":\"fill\",\"before\":\"\{n.style.fill}\",\"after\":\"\{resolved}\"}",
                )
                // 检查子节点文字对比度
                let fg = n.style.text_color
                let ratio = contrast_ratio(fg, resolved)
                if ratio < 4.5 {
                  impacts.push(
                    "{\"type\":\"accessibility\",\"warning\":\"文字对比度将降至 \{ratio.to_string()}(低于 4.5:1)\",\"suggestion\":\"同时更新 text_color\"}",
                  )
                }
              }
            }
            None => ()
          }
          // 尺寸变更影响
          match (patch.width, patch.height) {
            (Some(new_w), Some(new_h)) => {
              let layout = solve(ab.doc)
              match layout.rect_of(node_id) {
                Some(r) => {
                  let new_w_val = spec_val(new_w)
                  let new_h_val = spec_val(new_h)
                  if new_w_val != r.width || new_h_val != r.height {
                    impacts.push(
                      "{\"type\":\"layout\",\"field\":\"size\",\"before\":\"\{r.width.to_int()}×\{r.height.to_int()}\",\"after\":\"...\"}",
                    )
                    // 检查是否会导致溢出
                    match layout.rect_of("root") {
                      Some(root_r) => {
                        let new_right = r.x + new_w_val
                        let new_bottom = r.y + new_h_val
                        if new_right > root_r.right() ||
                          new_bottom > root_r.bottom() {
                          impacts.push(
                            "{\"type\":\"predicate\",\"warning\":\"尺寸变更将导致 contained_in_parent 违规\",\"suggestion\":\"auto_fix 可自动处理\"}",
                          )
                        }
                      }
                      None => ()
                    }
                  }
                }
                None => ()
              }
            }
            _ => ()
          }
          // 位置变更影响
          match (patch.x, patch.y) {
            (Some(_), _) | (_, Some(_)) =>
              impacts.push(
                "{\"type\":\"layout\",\"field\":\"position\",\"note\":\"位置变更可能影响兄弟节点间距和对齐\"}",
              )
            _ => ()
          }
          let j = jstr15(impacts)
          Ok(
            "{\"node\":\"\{node_id}\",\"impact_count\":\{impacts.length()},\"impacts\":[\{j}]}",
          )
        }
      }
  }
}

// ---------------------------------------------------------------------------
// 设计系统审计
// ---------------------------------------------------------------------------

///|
/// 审计整个项目:找出所有偏离设计系统的硬编码值
pub fn Project::audit_design_system(self : Project) -> String {
  let issues : Array[String] = []
  for ab_id in self.artboards.keys() {
    match self.artboards.get(ab_id) {
      Some(ab) =>
        for node_id in ab.doc.nodes.keys() {
          match ab.doc.nodes.get(node_id) {
            Some(n) => {
              // 检查填充色
              if n.style.fill != "none" && n.style.fill != "" {
                if !is_token_color2(self.tokens, n.style.fill) {
                  match nearest_token2(self.tokens, n.style.fill) {
                    Some((token_name, _)) =>
                      issues.push(
                        "{\"artboard\":\"\{ab_id}\",\"node\":\"\{node_id}\",\"field\":\"fill\",\"current\":\"\{n.style.fill}\",\"suggested_token\":\"\{token_name}\"}",
                      )
                    None =>
                      issues.push(
                        "{\"artboard\":\"\{ab_id}\",\"node\":\"\{node_id}\",\"field\":\"fill\",\"current\":\"\{n.style.fill}\",\"suggested_token\":null}",
                      )
                  }
                }
              }
              // 检查字号
              let standard_sizes = [
                10.0, 12.0, 14.0, 16.0, 20.0, 24.0, 32.0, 48.0,
              ]
              let is_standard = standard_sizes
                .iter()
                .any(fn(s) { s == n.style.font_size })
              if !is_standard && n.style.font_size != 14.0 {
                let nearest_size = nearest_in_list(
                  standard_sizes,
                  n.style.font_size,
                )
                issues.push(
                  "{\"artboard\":\"\{ab_id}\",\"node\":\"\{node_id}\",\"field\":\"font_size\",\"current\":\{n.style.font_size},\"suggested\":\{nearest_size}}",
                )
              }
            }
            None => ()
          }
        }
      None => ()
    }
  }
  let j = jstr15(issues)
  "{\"total_issues\":\{issues.length()},\"issues\":[\{j}]}"
}

///|
fn is_token_color2(tokens : DesignTokens, hex : String) -> Bool {
  for k in tokens.colors.keys() {
    match tokens.colors.get(k) {
      Some(v) => if v == hex { return true }
      None => ()
    }
  }
  false
}

///|
fn nearest_token2(tokens : DesignTokens, hex : String) -> (String, String)? {
  let target = parse_hex_rgb2(hex)
  let mut best : (String, String)? = None
  let mut best_dist = 999999.0
  for k in tokens.colors.keys() {
    match tokens.colors.get(k) {
      Some(v) => {
        let c = parse_hex_rgb2(v)
        let dist = color_dist2(target, c)
        if dist < best_dist {
          best_dist = dist
          best = Some((k, v))
        }
      }
      None => ()
    }
  }
  if best_dist < 60.0 {
    best
  } else {
    None
  }
}

///|
fn parse_hex_rgb2(hex : String) -> (Double, Double, Double) {
  let body = if hex.length() > 0 && hex.get_char(0) == Some('#') {
    hex.substring(start=1)
  } else {
    hex
  }
  (hex_part2(body, 0), hex_part2(body, 2), hex_part2(body, 4))
}

///|
fn hex_part2(s : String, offset : Int) -> Double {
  if s.length() < offset + 2 {
    return 0.0
  }
  let hi = hdv2(s.get_char(offset))
  let lo = hdv2(s.get_char(offset + 1))
  (hi * 16 + lo).to_double()
}

///|
fn hdv2(c : Char?) -> Int {
  match c {
    Some(ch) => {
      let code = ch.to_int()
      if code >= 48 && code <= 57 {
        code - 48
      } else if code >= 97 && code <= 102 {
        code - 87
      } else if code >= 65 && code <= 70 {
        code - 55
      } else {
        0
      }
    }
    None => 0
  }
}

///|
fn color_dist2(
  a : (Double, Double, Double),
  b : (Double, Double, Double),
) -> Double {
  let dr = a.0 - b.0
  let dg = a.1 - b.1
  let db = a.2 - b.2
  Double::sqrt(dr * dr + dg * dg + db * db)
}

///|
fn nearest_in_list(list : Array[Double], target : Double) -> Double {
  let mut best = list[0]
  let mut best_diff = (target - list[0]).abs()
  for v in list.iter() {
    let d = (target - v).abs()
    if d < best_diff {
      best_diff = d
      best = v
    }
  }
  best
}

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

///|
fn spec_val(s : SizeSpec) -> Double {
  match s {
    Fixed(v) => v
    _ => 0.0
  }
}