/// 智能对齐引擎:让 Agent 的"随手放"变成"精确设计"。
///
/// 设计师拖拽时会看到辅助线、吸附、等间距提示。
/// Agent 的等价体验是:放置后调用 `smart_align` 获得专业排版。
///
/// - auto_arrange:所有子元素垂直等间距排列
/// - align_row:水平一行等间距
/// - snap_to_grid:所有位置吸附到 N px 网格
/// - center_in_parent:居中对齐
/// - suggest_alignment:检测近似对齐并建议吸附

// ---------------------------------------------------------------------------
// 自动排列
// ---------------------------------------------------------------------------

///|
/// 所有子元素垂直等间距排列(容器内)
pub fn Project::auto_arrange(
  self : Project,
  artboard~ : String,
  parent_id? : String = "root",
) -> Result[String, String] {
  match self.artboards.get(artboard) {
    None => Err("unknown_artboard")
    Some(ab) =>
      match ab.doc.nodes.get(parent_id) {
        None => Err("unknown_parent")
        Some(parent) => {
          let n_children = parent.children.length()
          if n_children < 2 {
            return Ok(
              "{\"ok\":true,\"message\":\"少于2个子元素,无需排列\"}",
            )
          }
          // 计算总高度和可用空间
          let layout = solve(ab.doc)
          let parent_rect = match layout.rect_of(parent_id) {
            Some(r) => r
            None => return Err("layout_error")
          }
          let padding = 16.0
          let gap = 12.0
          let avail_h = parent_rect.height -
            padding * 2.0 -
            gap * (n_children - 1).to_double()
          let item_h = avail_h / n_children.to_double()
          let item_h = if item_h > 80.0 {
            80.0
          } else if item_h < 36.0 {
            36.0
          } else {
            item_h
          }
          // 重新排列
          let mut y = parent_rect.y + padding
          let fixed : Array[String] = []
          for cid in parent.children.iter() {
            match ab.doc.nodes.get(cid) {
              Some(child) => {
                let cur_h = match layout.rect_of(cid) {
                  Some(r) => r.height
                  None => item_h
                }
                let h = if cur_h > 0.0 { cur_h } else { item_h }
                let _ = ab.doc.update_node(cid, fn(c) {
                  {
                    ..c,
                    x: AlignStart(padding),
                    y: AlignStart(y - parent_rect.y),
                    width: Fill,
                    height: Fixed(h),
                  }
                })
                fixed.push(
                  "{\"id\":\"\{cid}\",\"y\":\{y.to_int()},\"h\":\{h.to_int()}}",
                )
                y = y + h + gap
              }
              None => ()
            }
          }
          let j = jstr10(fixed)
          Ok("{\"ok\":true,\"arranged\":\{n_children},\"detail\":[\{j}]}")
        }
      }
  }
}

// ---------------------------------------------------------------------------
// 网格吸附
// ---------------------------------------------------------------------------

///|
/// 所有节点位置吸附到 N px 网格
pub fn Project::snap_to_grid(
  self : Project,
  artboard~ : String,
  grid? : Double = 8.0,
) -> Result[String, String] {
  match self.artboards.get(artboard) {
    None => Err("unknown_artboard")
    Some(ab) => {
      let snapped : Array[String] = []
      // 先解一次布局取全部绝对矩形做吸附快照,再自根向下统一写回:
      // 写回值是「相对父容器新原点」的偏移(父也在移动),与 Map 迭序无关。
      // 直接把绝对吸附坐标当相对偏移写回,会让嵌套节点整体偏移一个父原点。
      let layout = solve(ab.doc)
      let targets : Map[String, (Double, Double)] = Map::new()
      for id in ab.doc.nodes.keys() {
        if id != "root" {
          match layout.rect_of(id) {
            Some(r) => {
              let new_x = snap_val(r.x, grid)
              let new_y = snap_val(r.y, grid)
              if new_x != r.x || new_y != r.y {
                targets.set(id, (new_x, new_y))
              }
            }
            None => ()
          }
        }
      }
      // DFS(父先于子):子节点用父的新绝对原点换算 AlignStart 偏移
      let stack : Array[(String, Double, Double)] = []
      match layout.rect_of(ab.doc.root_id) {
        Some(rr) => stack.push((ab.doc.root_id, rr.x, rr.y))
        None => ()
      }
      while stack.length() > 0 {
        match stack.pop() {
          None => ()
          Some((id, px, py)) => {
            let (nx, ny) = match targets.get(id) {
              Some((tx, ty)) => {
                let (ox, oy) = match layout.rect_of(id) {
                  Some(r) => (r.x, r.y)
                  None => (tx, ty)
                }
                let _ = ab.doc.update_node(id, fn(c) {
                  {
                    ..c,
                    x: AlignStart(tx - px),
                    y: AlignStart(ty - py),
                  }
                })
                snapped.push(
                  "{\"id\":\"\{id}\",\"from\":\"\{ox.to_int()},\{oy.to_int()}\",\"to\":\"\{tx.to_int()},\{ty.to_int()}\"}",
                )
                (tx, ty)
              }
              None =>
                match layout.rect_of(id) {
                  Some(r) => (r.x, r.y)
                  None => (px, py)
                }
            }
            match ab.doc.nodes.get(id) {
              Some(node) =>
                for cid in node.children.iter() {
                  stack.push((cid, nx, ny))
                }
              None => ()
            }
          }
        }
      }
      let j = jstr10(snapped)
      Ok(
        "{\"ok\":true,\"snapped\":\{snapped.length()},\"grid\":\{grid},\"detail\":[\{j}]}",
      )
    }
  }
}

///|
fn snap_val(v : Double, grid : Double) -> Double {
  if grid <= 0.0 {
    return v
  }
  (v / grid).to_int().to_double() * grid
}

// ---------------------------------------------------------------------------
// 居中对齐
// ---------------------------------------------------------------------------

///|
/// 将节点在父容器内水平居中
pub fn Project::center_in_parent(
  self : Project,
  artboard~ : String,
  node_id~ : String,
) -> Result[String, String] {
  match self.artboards.get(artboard) {
    None => Err("unknown_artboard")
    Some(ab) =>
      match ab.doc.find_parent(node_id) {
        None => Err("no_parent")
        Some(pid) => {
          let layout = solve(ab.doc)
          match (layout.rect_of(node_id), layout.rect_of(pid)) {
            (Some(nr), Some(pr)) => {
              let centered_x = pr.x + (pr.width - nr.width) / 2.0
              let _ = ab.doc.update_node(node_id, fn(c) {
                { ..c, x: AlignStart(centered_x - pr.x), }
              })
              Ok(
                "{\"ok\":true,\"id\":\"\{node_id}\",\"x\":\{centered_x.to_int()}}",
              )
            }
            _ => Err("layout_error")
          }
        }
      }
  }
}

// ---------------------------------------------------------------------------
// 对齐建议(检测近似对齐)
// ---------------------------------------------------------------------------

///|
pub(all) struct AlignmentHint {
  node_a : String
  node_b : String
  axis : String // "x" | "y" | "right" | "center_x"
  delta : Double // 当前偏差
  suggestion : String // 人可读建议
}

///|
/// 检测近似对齐并给出建议
pub fn Project::suggest_alignment(
  self : Project,
  artboard~ : String,
) -> Result[String, String] {
  match self.artboards.get(artboard) {
    None => Err("unknown_artboard")
    Some(ab) => {
      let layout = solve(ab.doc)
      let ids : Array[String] = []
      for id in ab.doc.nodes.keys() {
        if id != "root" {
          ids.push(id)
        }
      }
      let hints : Array[String] = []
      let threshold = 4.0 // 小于 4px 视为"接近对齐"
      for i in 0.. {
              // 左边缘接近
              let dx = ra.x - rb.x
              if dx != 0.0 && dx.abs() < threshold {
                let s = "左边缘偏差 \{dx.to_string()}px:\{ids[i]} 和 \{ids[j]} 左对齐"
                hints.push(
                  "{\"type\":\"left\",\"a\":\"\{ids[i]}\",\"b\":\"\{ids[j]}\",\"delta\":\{dx},\"suggestion\":\"\{s}\"}",
                )
              }
              // 顶边缘接近
              let dy = ra.y - rb.y
              if dy != 0.0 && dy.abs() < threshold {
                let s2 = "顶边缘偏差 \{dy.to_string()}px:\{ids[i]} 和 \{ids[j]} 顶对齐"
                hints.push(
                  "{\"type\":\"top\",\"a\":\"\{ids[i]}\",\"b\":\"\{ids[j]}\",\"delta\":\{dy},\"suggestion\":\"\{s2}\"}",
                )
              }
              // 水平中心接近
              let ca = ra.x + ra.width / 2.0
              let cb = rb.x + rb.width / 2.0
              let dc = ca - cb
              if dc != 0.0 && dc.abs() < threshold {
                let s3 = "水平中心偏差 \{dc.to_string()}px:\{ids[i]} 和 \{ids[j]} 居中对齐"
                hints.push(
                  "{\"type\":\"center_x\",\"a\":\"\{ids[i]}\",\"b\":\"\{ids[j]}\",\"delta\":\{dc},\"suggestion\":\"\{s3}\"}",
                )
              }
            }
            _ => ()
          }
        }
      }
      let hj = jstr10(hints)
      Ok("{\"ok\":true,\"hints\":\{hints.length()},\"detail\":[\{hj}]}")
    }
  }
}

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