///|
/// AI 设计批评引擎:像资深设计师一样评审原型。
///
/// 不是检查规则(那是 lint),而是基于**设计原则**给出整体评价:
/// 1. 视觉层次 — 有没有清晰的"第一眼看到什么"
/// 2. 亲密性 — 相关元素是否靠近(格式塔原理)
/// 3. 对齐 — 是否有一致的视觉轴线
/// 4. 一致性 — 相似元素是否有相似处理
/// 5. 留白 — 是否有足够的呼吸空间
/// 6. 平衡 — 视觉重量是否分布合理
/// 7. 焦点 — 是否有明确的主动作(CTA)
/// 8. 节奏 — 间距和尺寸是否有韵律感
///
/// 输出:结构化评审报告(每个原则一个评分 + 具体建议)
pub(all) struct CritiqueResult {
  principle : String
  score : Int // 0-10
  verdict : String // "good" | "fair" | "poor"
  observation : String // 观察到什么
  advice : String // 怎么改
}

///|
/// 综合评审:对画板进行 8 项设计原则评审
pub fn Project::critique(
  self : Project,
  artboard~ : String,
) -> Result[String, String] {
  match self.artboards.get(artboard) {
    None => Err("unknown_artboard")
    Some(ab) => {
      let results : Array[String] = []
      let layout = solve(ab.doc)
      let doc = ab.doc

      // ── 1. 视觉层次 ──
      let font_sizes : Map[Double, Int] = Map::new()
      for id in doc.nodes.keys() {
        match doc.nodes.get(id) {
          Some(n) =>
            match font_sizes.get(n.style.font_size) {
              Some(c) => font_sizes.set(n.style.font_size, c + 1)
              None => font_sizes.set(n.style.font_size, 1)
            }
          None => ()
        }
      }
      let distinct_sizes = font_sizes.size()
      let hierarchy_score = if distinct_sizes >= 3 {
        9
      } else if distinct_sizes == 2 {
        6
      } else {
        3
      }
      let hierarchy_verdict = if hierarchy_score >= 7 {
        "good"
      } else if hierarchy_score >= 5 {
        "fair"
      } else {
        "poor"
      }
      let hierarchy_obs = "\{distinct_sizes} 种字号"
      let hierarchy_advice = if distinct_sizes < 3 {
        "增加字号层次:标题用 h2/h3,正文用 body,辅助信息用 caption"
      } else {
        "层次清晰"
      }
      results.push(
        crit_json(
          "visual_hierarchy", hierarchy_score, hierarchy_verdict, hierarchy_obs,
          hierarchy_advice,
        ),
      )

      // ── 2. 亲密性(Gestalt)──
      let mut tight_groups = 0
      let mut total_groups = 0
      for id in doc.nodes.keys() {
        match doc.nodes.get(id) {
          Some(n) =>
            if n.children.length() > 1 {
              total_groups = total_groups + 1
              match n.layout {
                Some(l) => if l.gap < 4.0 { tight_groups = tight_groups + 1 }
                None => ()
              }
            }
          None => ()
        }
      }
      let prox_score = if total_groups == 0 {
        5
      } else if tight_groups == 0 {
        8
      } else if tight_groups <= total_groups / 2 {
        6
      } else {
        3
      }
      let prox_verdict = if prox_score >= 7 {
        "good"
      } else if prox_score >= 5 {
        "fair"
      } else {
        "poor"
      }
      results.push(
        crit_json(
          "proximity",
          prox_score,
          prox_verdict,
          "\{tight_groups}/\{total_groups} 容器间距过紧",
          "确保相关元素间距 ≥ 8px,不相关元素间距 ≥ 24px",
        ),
      )

      // ── 3. 对齐 ──
      let mut aligned_count = 0
      let mut total_check = 0
      for id in doc.nodes.keys() {
        match layout.rect_of(id) {
          Some(r) =>
            if id != "root" {
              total_check = total_check + 1
              // 检查左边缘是否对齐到 8px 网格
              if r.x % 8.0 < 1.0 || r.x % 8.0 > 7.0 {
                aligned_count = aligned_count + 1
              }
            }
          None => ()
        }
      }
      let align_score = if total_check == 0 {
        5
      } else {
        aligned_count * 10 / total_check
      }
      let align_verdict = if align_score >= 7 {
        "good"
      } else if align_score >= 5 {
        "fair"
      } else {
        "poor"
      }
      results.push(
        crit_json(
          "alignment",
          align_score,
          align_verdict,
          "\{aligned_count}/\{total_check} 节点对齐到网格",
          "运行 snap_to_grid(8) 统一对齐",
        ),
      )

      // ── 4. 一致性(颜色)──
      let mut token_count = 0
      let mut color_count = 0
      for id in doc.nodes.keys() {
        match doc.nodes.get(id) {
          Some(n) =>
            if n.style.fill != "none" && n.style.fill != "" {
              color_count = color_count + 1
              if is_token_color3(self.tokens, n.style.fill) {
                token_count = token_count + 1
              }
            }
          None => ()
        }
      }
      let cons_score = if color_count == 0 {
        7
      } else {
        token_count * 10 / color_count
      }
      let cons_verdict = if cons_score >= 7 {
        "good"
      } else if cons_score >= 5 {
        "fair"
      } else {
        "poor"
      }
      results.push(
        crit_json(
          "consistency",
          cons_score,
          cons_verdict,
          "\{token_count}/\{color_count} 颜色使用令牌",
          "运行 audit_design_system 查看硬编码颜色",
        ),
      )

      // ── 5. 留白 ──
      let node_count = doc.nodes.size() - 1
      let area = ab.width * ab.height
      let density = if area > 0.0 {
        node_count.to_double() / (area / 10000.0)
      } else {
        0.0
      }
      let white_score = if density < 2.0 {
        9
      } else if density < 4.0 {
        7
      } else if density < 6.0 {
        5
      } else {
        3
      }
      let white_verdict = if white_score >= 7 {
        "good"
      } else if white_score >= 5 {
        "fair"
      } else {
        "poor"
      }
      results.push(
        crit_json(
          "whitespace",
          white_score,
          white_verdict,
          "密度 \{density.to_string()} 节点/万px²",
          if density > 4.0 {
            "减少元素或增大画板"
          } else {
            "留白充分"
          },
        ),
      )

      // ── 6. 平衡 ──
      let mut left_weight = 0.0
      let mut right_weight = 0.0
      for id in doc.nodes.keys() {
        match layout.rect_of(id) {
          Some(r) =>
            if id != "root" {
              let center_x = r.x + r.width / 2.0
              let weight = r.width * r.height
              if center_x < ab.width / 2.0 {
                left_weight = left_weight + weight
              } else {
                right_weight = right_weight + weight
              }
            }
          None => ()
        }
      }
      let total_w = left_weight + right_weight
      let balance_ratio = if total_w > 0.0 {
        left_weight / total_w
      } else {
        0.5
      }
      let bal_score = if balance_ratio > 0.3 && balance_ratio < 0.7 {
        8
      } else if balance_ratio > 0.2 && balance_ratio < 0.8 {
        6
      } else {
        4
      }
      let bal_verdict = if bal_score >= 7 {
        "good"
      } else if bal_score >= 5 {
        "fair"
      } else {
        "poor"
      }
      let left_pct = (balance_ratio * 100.0).to_int().to_string()
      let right_pct = ((1.0 - balance_ratio) * 100.0).to_int().to_string()
      let bal_obs = "左" + left_pct + "% / 右" + right_pct + "%"
      let bal_advice = if bal_score < 6 {
        "调整元素分布使左右视觉重量更均衡"
      } else {
        "平衡良好"
      }
      results.push(
        crit_json("balance", bal_score, bal_verdict, bal_obs, bal_advice),
      )

      // ── 7. 焦点 ──
      let mut has_primary_cta = false
      let mut button_count = 0
      for id in doc.nodes.keys() {
        match doc.nodes.get(id) {
          Some(n) =>
            if n.component == "button" {
              button_count = button_count + 1
              if n.style.fill == self.tokens.resolve_color("primary") {
                has_primary_cta = true
              }
            }
          None => ()
        }
      }
      let focus_score = if has_primary_cta && button_count <= 3 {
        9
      } else if has_primary_cta {
        6
      } else if button_count > 0 {
        4
      } else {
        5
      }
      let focus_verdict = if focus_score >= 7 {
        "good"
      } else if focus_score >= 5 {
        "fair"
      } else {
        "poor"
      }
      results.push(
        crit_json(
          "focus",
          focus_score,
          focus_verdict,
          "\{button_count} 个按钮, 主CTA: \{has_primary_cta}",
          if !has_primary_cta {
            "为最重要的操作使用 primary 变体按钮"
          } else {
            "焦点明确"
          },
        ),
      )

      // ── 8. 节奏 ──
      let gaps : Map[Double, Int] = Map::new()
      for id in doc.nodes.keys() {
        match doc.nodes.get(id) {
          Some(n) =>
            match n.layout {
              Some(l) =>
                match gaps.get(l.gap) {
                  Some(c) => gaps.set(l.gap, c + 1)
                  None => gaps.set(l.gap, 1)
                }
              None => ()
            }
          None => ()
        }
      }
      let distinct_gaps = gaps.size()
      let rhythm_score = if distinct_gaps <= 3 {
        8
      } else if distinct_gaps <= 5 {
        6
      } else {
        4
      }
      let rhythm_verdict = if rhythm_score >= 7 {
        "good"
      } else if rhythm_score >= 5 {
        "fair"
      } else {
        "poor"
      }
      results.push(
        crit_json(
          "rhythm",
          rhythm_score,
          rhythm_verdict,
          "\{distinct_gaps} 种间距值",
          if distinct_gaps > 3 {
            "统一间距到标准令牌(4/8/12/16/24)"
          } else {
            "节奏统一"
          },
        ),
      )

      // ── 9. 组件规范(norms:按钮不卡文案 · 方正圆润 · 全宽条直角)──
      // 与 auto_fix 的 norm_fix_pass 同一判据:critique 指出偏差并给出建议尺寸。
      // 建议按来源区分修复路径(norm_issue_hint):v1 五规则/触控/返回在
      // auto_fix 的执行范围内(norm_fix_pass / norm_touch_fix_pass /
      // list_divider),给 auto_fix 指引;字号阶/密度/gap 网格是 v2 检测项、
      // auto_fix 没有对应修复通道——给具体改法,不宣称一键修复。
      let prof = self.norms_profile()
      let norm_list = norm_issues_v2(
        doc,
        layout,
        radius_base=prof.radius_base,
        contrast_min=prof.contrast_min,
        touch_min=prof.touch_min,
      )
      // 返回交互校验(返回是必须的):非入口画板无出边 flow 且无返回类节点
      // = 用户会卡死——追加进 norms 维度,与检测/修复同一报告
      for s in self.norm_back_issues(artboard=artboard).iter() {
        norm_list.push(s)
      }
      // issue #23:状态可辨识(选中态无差异 / 纯黑选中色)
      for s in self.norm_selected_state_issues(artboard=artboard).iter() {
        norm_list.push(s)
      }
      // issue #25/#26:交互与内容完整性(可交互无去向 / 占位符 / 空壳 / 非库组件)
      for s in self.qa_issues(artboard=artboard).iter() {
        norm_list.push(s)
      }
      let n_norm = norm_list.length()
      let norm_score = if n_norm == 0 {
        9
      } else if n_norm <= 2 {
        7
      } else if n_norm <= 4 {
        5
      } else {
        3
      }
      let norm_verdict = if norm_score >= 7 {
        "good"
      } else if norm_score >= 5 {
        "fair"
      } else {
        "poor"
      }
      let norm_advice = if n_norm == 0 {
        "组件规范良好(按钮留白 · 圆角档 · 全宽条直角)"
      } else {
        let mut acc = ""
        for i in 0..= 2 {
            break
          }
          acc = acc + norm_list[i] + "→ " + norm_issue_hint(norm_list[i]) + ";"
        }
        acc
      }
      results.push(
        crit_json("norms", norm_score, norm_verdict, "\{n_norm} 处规范偏差", norm_advice),
      )

      let j = jstr19(results)
      Ok("{\"principles\":9,\"results\":[\{j}]}")
    }
  }
}

///|
/// 评审摘要(一行)
pub fn Project::critique_summary(
  self : Project,
  artboard~ : String,
) -> Result[String, String] {
  match self.critique(artboard~) {
    Err(e) => Err(e)
    Ok(json) => {
      // 计算平均分
      let scores = extract_scores(json)
      let total = sum_scores(scores)
      let avg = if scores.length() > 0 { total / scores.length() } else { 0 }
      let grade = if avg >= 8 {
        "A"
      } else if avg >= 6 {
        "B"
      } else if avg >= 4 {
        "C"
      } else {
        "D"
      }
      Ok("Design Critique: \{grade} (\{avg}/10)")
    }
  }
}

///|
/// norms 建议按 issue 来源区分修复路径:字号阶(type scale)/密度(node
/// count)/gap 网格(not on 4pt grid)是 v2 检测项,auto_fix 没有对应修复
/// 通道——给具体改法;其余(v1 五规则/触控/返回)给 auto_fix 指引。
fn norm_issue_hint(issue : String) -> String {
  if issue.contains("not on 4pt grid") {
    "gap 调到 4 的倍数(4/8/12/16)"
  } else if issue.contains("type scale") {
    "字号收敛到 ≤5 档(如 12/14/16/20/24)"
  } else if issue.contains("node count") {
    "拆分画板或删减装饰节点"
  } else if issue.contains("可读性标准") {
    "加深文字或调整背景色(正文 4.5 / 大字号 3.0)"
  } else if issue.contains("组件不可见") {
    "换填充色或删除该节点"
  } else if issue.contains("纯黑") {
    "深色底改用主题令牌,不要硬填 #000/#111"
  } else if issue.contains("无差异") {
    "选中态至少改 fill / text_color / stroke"
  } else if issue.contains("可交互组件无去向") {
    "绑定 tap flow(flow <板> <目标板> <节点>)或改用非交互组件"
  } else if issue.contains("空壳容器") {
    "填 ≥2 条语义化示例条目(真实文案,非 Item 1/项目一)"
  } else if issue.contains("占位符残留") {
    "换语义化真实文案,去掉 TBD/TODO/xxx 一类占位"
  } else if issue.contains("非组件库组件") {
    "换组件库注册表内的组件(list-components 可查)"
  } else {
    "auto_fix 可一键修复"
  }
}

///|
fn crit_json(
  principle : String,
  score : Int,
  verdict : String,
  obs : String,
  advice : String,
) -> String {
  "{\"principle\":\"\{principle}\",\"score\":\{score},\"verdict\":\"\{verdict}\",\"observation\":\"\{obs}\",\"advice\":\"\{advice}\"}"
}

///|
fn is_token_color3(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 extract_scores(json : String) -> Array[Int] {
  let scores : Array[Int] = []
  let parts = json.split(",\"score\":")
  // 首段是信封头({"principles":9,"results":[…"principle":"…")而非评分,
  // 跳过——否则 extract_leading_int 会抓到头部的 9,均值变成 (9+Σ九项)/10,
  // 档位边界虚高一级
  let mut first = true
  for p0 in parts.iter() {
    if first {
      first = false
      continue
    }
    let num = extract_leading_int(p0.to_string())
    if num >= 0 {
      scores.push(num)
    }
  }
  scores
}

///|
fn extract_leading_int(s : String) -> Int {
  let mut result = 0
  let mut found = false
  for c in s.iter() {
    let code = c.to_int()
    if code >= 48 && code <= 57 {
      result = result * 10 + (code - 48)
      found = true
    } else if found {
      break
    }
  }
  if found {
    result
  } else {
    -1
  }
}

///|
fn sum_scores(scores : Array[Int]) -> Int {
  let mut total = 0
  for s in scores.iter() {
    total = total + s
  }
  total
}

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