// Copyright (c) 2026 BigSaltyMan. BSL-1.0; see LICENSE.
// Decimal storage and metered adaptation of MoonClip's validated integer tree.

///|
// Retain parent indices for exact topology correspondence; no public parent view.
#warnings("-unused_field")
priv struct TreeNodeD {
  contour : PathD
  parent : Int?
  children : Array[Int]
  depth : Int
}

///|
priv struct TreeDataD {
  nodes : Array[TreeNodeD]
  roots : Array[Int]
}

///|
/// Immutable topology from the quantized integer region. Introduced in 0.2.0.
pub struct PolyTreeD {
  priv data : TreeDataD
}

///|
pub struct PolyNodeD {
  priv data : TreeDataD
  priv index : Int
}

///|
/// Returns a fresh array of immutable views. Root contours have depth one.
pub fn PolyTreeD::roots(self : PolyTreeD) -> Array[PolyNodeD] {
  self.data.roots.map(fn(index) { { data: self.data, index, } })
}

///|
/// Copies both array levels; explicit parent-child relationships are discarded.
pub fn PolyTreeD::to_paths(self : PolyTreeD) -> PathsD {
  self.data.nodes.map(fn(node) { node.contour.copy() })
}

///|
pub fn PolyNodeD::contour(self : PolyNodeD) -> PathD {
  self.data.nodes[self.index].contour.copy()
}

///|
pub fn PolyNodeD::children(self : PolyNodeD) -> Array[PolyNodeD] {
  self.data.nodes[self.index].children.map(fn(index) {
    { data: self.data, index, }
  })
}

///|
pub fn PolyNodeD::depth(self : PolyNodeD) -> Int {
  self.data.nodes[self.index].depth
}

///|
pub fn PolyNodeD::is_hole(self : PolyNodeD) -> Bool {
  self.depth() % 2 == 0
}

///|
// Logical allocation units (not bytes): 4 + 5*N + 2*V, since the total
// root/child index count equals N. Work: 1 + 2*N + V. Charge before allocating;
// no refund for the integer tree. Every failure uses output, never input indices.
fn decimal_tree_output(
  tree : PolyTree64,
  precision : Precision,
  budget : CallBudget,
) -> PolyTreeD raise ScanAbort {
  let loc = location(DecimalOutput, None, None)
  decimal_spend(budget, 1, 0, loc)
  let nodes : Array[TreeNodeD] = []
  decimal_spend(budget, 1, 0, loc)
  let roots : Array[Int] = []
  for node in tree.data.nodes {
    // Node record, node slot, contour container, child-index container.
    decimal_spend(budget, 4, 1, loc)
    let contour : PathD = []
    let children : Array[Int] = []
    for p in node.contour {
      // Immutable PointD and its array slot; both coordinate decodes are one
      // vertex work unit, matching decimal_output's existing convention.
      decimal_spend(budget, 2, 1, loc)
      contour.push(
        PointD::new(
          decimal_value(p.x, precision),
          decimal_value(p.y, precision),
        ),
      )
    }
    for index in node.children {
      decimal_spend(budget, 1, 1, loc)
      children.push(index)
    }
    nodes.push({ contour, parent: node.parent, children, depth: node.depth, })
  }
  for index in tree.data.roots {
    decimal_spend(budget, 1, 1, loc)
    roots.push(index)
  }
  decimal_spend(budget, 2, 1, loc)
  { data: { nodes, roots, }, }
}