/// 补丁协议(docs/05):两条路线共用的变更语言。
///
/// 事务语义:先在候选副本上应用全部操作并校验,通过才整体合并——
/// 要么全部生效,要么什么都不改。Agent 路线任何违规都会被拒(硬门槛),
/// 人类路线只有结构性违规被拒。

///|
/// 节点字段补丁:None = 不修改
pub(all) struct NodePatch {
  name : String?
  width : SizeSpec?
  height : SizeSpec?
  x : PosSpec?
  y : PosSpec?
  text : String?
  layout_dir : StackDir?
  layout_gap : Double?
  layout_justify : Justify?
  layout_padding : Padding?
  fill : String?
  stroke : String?
  corner_radius : Double?
  font_size : Double?
  text_color : String?
}

///|
pub fn NodePatch::empty() -> NodePatch {
  {
    name: None,
    width: None,
    height: None,
    x: None,
    y: None,
    text: None,
    layout_dir: None,
    layout_gap: None,
    layout_justify: None,
    layout_padding: None,
    fill: None,
    stroke: None,
    corner_radius: None,
    font_size: None,
    text_color: None,
  }
}

///|
pub(all) enum Op {
  InsertNode(Node, String) // 新节点, 父节点 id(追加到 children 末尾)
  UpdateNode(String, NodePatch) // 目标 id, 字段补丁
  MoveNode(String, String) // 目标 id, 新父 id
  DeleteNode(String) // 删除节点及其子树
}

///|
pub(all) struct Patch {
  base_revision : Int
  message : String
  ops : Array[Op]
}

///|
pub(all) enum PatchError {
  StaleBase(Int) // 携带文档当前 revision:需重新读取后提交
  UnknownNode(String)
  UnknownParent(String)
  DuplicateId(String)
  HierarchyError(String) // 移入自己的子树、删除根节点等
  StructuralBlock(Array[Violation]) // 结构谓词失败(两条路线都拒绝)
  HardBlock(Array[Violation]) // 视觉谓词失败(仅 Agent 路线拒绝)
}

///|
/// Agent 路线入口:硬门槛。成功返回空违规集(永远为空)。
pub fn apply_agent_patch(
  doc : Document,
  patch : Patch,
) -> Result[Array[Violation], PatchError] {
  apply_inner(doc, patch, AgentGate)
}

///|
/// 人类路线入口:结构硬、视觉软。成功时返回携带的视觉债。
pub fn apply_human_op(
  doc : Document,
  patch : Patch,
) -> Result[Array[Violation], PatchError] {
  apply_inner(doc, patch, HumanGate)
}

///|
fn apply_inner(
  doc : Document,
  patch : Patch,
  gate : Gate,
) -> Result[Array[Violation], PatchError] {
  if patch.base_revision != doc.revision {
    return Err(StaleBase(doc.revision))
  }
  // 1) 在候选副本上应用全部操作
  let candidate = doc.clone()
  for op in patch.ops.iter() {
    match apply_op(candidate, op) {
      Err(e) => return Err(e)
      Ok(_) => ()
    }
  }
  // 2) 校验候选状态
  let violations = validate(candidate)
  // 3) 按路线策略决定是否合并
  match decide(gate, violations) {
    Reject(blocking) => {
      let err : PatchError = match gate {
        AgentGate => HardBlock(blocking)
        HumanGate => StructuralBlock(blocking)
      }
      Err(err)
    }
    Allow(debt) => {
      let source : OpSource = match gate {
        AgentGate => AgentOp
        HumanGate => HumanOp
      }
      doc.nodes = candidate.nodes
      doc.root_id = candidate.root_id
      doc.revision = doc.revision + 1
      doc.journal.push({
        revision: doc.revision,
        source,
        message: patch.message,
        violation_count: debt.length(),
      })
      Ok(debt)
    }
  }
}

///|
fn apply_op(doc : Document, op : Op) -> Result[Unit, PatchError] {
  match op {
    InsertNode(node, parent_id) => {
      if doc.has(node.id) {
        return Err(DuplicateId(node.id))
      }
      if !doc.has(parent_id) {
        return Err(UnknownParent(parent_id))
      }
      if doc.insert_node(node, parent_id) {
        Ok(())
      } else {
        Err(HierarchyError("插入失败"))
      }
    }
    UpdateNode(id, p) =>
      match doc.nodes.get(id) {
        None => Err(UnknownNode(id))
        Some(_) => {
          let _ = doc.update_node(id, fn(n) { apply_node_patch(n, p) })
          Ok(())
        }
      }
    MoveNode(id, new_parent) => {
      if !doc.has(id) {
        return Err(UnknownNode(id))
      }
      if !doc.has(new_parent) {
        return Err(UnknownParent(new_parent))
      }
      if id == doc.root_id {
        return Err(HierarchyError("不能移动根节点"))
      }
      if is_descendant(doc, id, new_parent) {
        return Err(
          HierarchyError("不能把节点移动到它自己的子树内"),
        )
      }
      match doc.find_parent(id) {
        None => Err(HierarchyError("找不到旧父节点"))
        Some(old_parent) => {
          detach_child(doc, old_parent, id)
          append_child(doc, new_parent, id)
          Ok(())
        }
      }
    }
    DeleteNode(id) => {
      if !doc.has(id) {
        return Err(UnknownNode(id))
      }
      if id == doc.root_id {
        return Err(HierarchyError("不能删除根节点"))
      }
      match doc.find_parent(id) {
        None => Err(HierarchyError("找不到父节点"))
        Some(parent_id) => {
          detach_child(doc, parent_id, id)
          delete_subtree(doc, id)
          Ok(())
        }
      }
    }
  }
}

///|
fn apply_node_patch(n : Node, p : NodePatch) -> Node {
  let n = match p.name {
    Some(v) => { ..n, name: v, }
    None => n
  }
  let n = match p.width {
    Some(v) => { ..n, width: v, }
    None => n
  }
  let n = match p.height {
    Some(v) => { ..n, height: v, }
    None => n
  }
  let n = match p.x {
    Some(v) => { ..n, x: v, }
    None => n
  }
  let n = match p.y {
    Some(v) => { ..n, y: v, }
    None => n
  }
  let n = match p.text {
    Some(v) => { ..n, text: v, }
    None => n
  }
  // 布局字段:任一出现即整块重建(无则按缺省布局初始化)
  let layout : StackLayout? = match
    (p.layout_dir, p.layout_gap, p.layout_justify, p.layout_padding) {
    (None, None, None, None) => n.layout
    _ => {
      let base : StackLayout = match n.layout {
        Some(b) => b
        None =>
          {
            dir: Vertical,
            gap: 0.0,
            justify: Start,
            padding: Padding::uniform(0.0),
          }
      }
      let dir : StackDir = match p.layout_dir {
        Some(d) => d
        None => base.dir
      }
      let gap : Double = match p.layout_gap {
        Some(g) => g
        None => base.gap
      }
      let justify : Justify = match p.layout_justify {
        Some(j) => j
        None => base.justify
      }
      let padding : Padding = match p.layout_padding {
        Some(pd) => pd
        None => base.padding
      }
      Some({ dir, gap, justify, padding, })
    }
  }
  let n = { ..n, layout, }
  let style = n.style
  let fill : String = match p.fill {
    Some(v) => v
    None => style.fill
  }
  let stroke : String = match p.stroke {
    Some(v) => v
    None => style.stroke
  }
  let corner_radius : Double = match p.corner_radius {
    Some(v) => v
    None => style.corner_radius
  }
  let font_size : Double = match p.font_size {
    Some(v) => v
    None => style.font_size
  }
  let text_color : String = match p.text_color {
    Some(v) => v
    None => style.text_color
  }
  {
    ..n,
    style: { ..style, fill, stroke, corner_radius, font_size, text_color, },
  }
}

///|
fn detach_child(doc : Document, parent_id : String, child_id : String) -> Unit {
  match doc.nodes.get(parent_id) {
    Some(p) => {
      let kids : Array[String] = Array::new()
      for c in p.children.iter() {
        if c != child_id {
          kids.push(c)
        }
      }
      doc.content_seq = doc.content_seq + 1
      doc.nodes.set(parent_id, { ..p, children: kids, })
    }
    None => ()
  }
}

///|
fn append_child(doc : Document, parent_id : String, child_id : String) -> Unit {
  match doc.nodes.get(parent_id) {
    Some(p) => {
      let kids = p.children.copy()
      kids.push(child_id)
      doc.content_seq = doc.content_seq + 1
      doc.nodes.set(parent_id, { ..p, children: kids, })
    }
    None => ()
  }
}

///|
fn delete_subtree(doc : Document, id : String) -> Unit {
  match doc.nodes.get(id) {
    Some(n) => {
      for c in n.children.iter() {
        delete_subtree(doc, c)
      }
      doc.content_seq = doc.content_seq + 1
      let _ = doc.nodes.remove(id)
    }
    None => ()
  }
}

///|
/// candidate 是否位于 ancestor 的子树内
fn is_descendant(doc : Document, ancestor : String, candidate : String) -> Bool {
  match doc.nodes.get(ancestor) {
    None => false
    Some(n) => {
      let mut found = false
      for c in n.children.iter() {
        if c == candidate {
          found = true
        } else if !found && is_descendant(doc, c, candidate) {
          found = true
        }
      }
      found
    }
  }
}