///|
/// One zone's proposed atomic transition. The origin must match both parsed
/// zone documents. At most six revisions can be checked in one plan.
pub(all) struct ZoneRevision {
  origin : String
  before : Zone
  after : Zone
} derive(Debug)

///|
pub(all) struct DeploymentSnapshot {
  mask : Int
  blocked : Bool
  findings : Array[DelegationFinding]
} derive(Debug)

///|
/// One attempted publication step from a reachable state into a blocked
/// state. `from_mask = -1` means the baseline itself is blocked.
pub(all) struct DeploymentCounterexample {
  from_mask : Int
  to_mask : Int
  changed_zone : String
  safe_prefix : Array[String]
  findings : Array[DelegationFinding]
} derive(Debug)

///|
/// A relation shared by every safe complete publication sequence.
pub(all) struct DeploymentPrecedence {
  before_origin : String
  after_origin : String
} derive(Debug)

///|
/// A safe order is a path through the Boolean state lattice where each
/// step updates exactly one zone and every visited snapshot passes.
pub(all) struct DeploymentPlan {
  status : String
  zone_order : Array[String]
  order : Array[String]
  snapshots : Array[DeploymentSnapshot]
  preflight : Array[DelegationFinding]
  counterexample : DeploymentCounterexample?
  safe_order_count : Int
  required_precedence : Array[DeploymentPrecedence]
  first_steps : Array[String]
  blocked_state_count : Int
} derive(Debug)

///|
fn deployment_preflight(
  revisions : Array[ZoneRevision],
) -> Array[DelegationFinding] {
  let findings : Array[DelegationFinding] = []
  if revisions.length() < 2 || revisions.length() > 6 {
    delegation_finding(
      findings, "M001", "error", "manifest", "", 0, "deployment plan requires 2 to 6 zones",
    )
    return findings
  }
  let seen : Map[String, Bool] = Map([])
  for revision in revisions {
    if seen.get(revision.origin) is Some(_) {
      delegation_finding(
        findings,
        "M002",
        "error",
        revision.origin,
        revision.origin,
        0,
        "zone origin occurs more than once in deployment plan",
      )
    }
    seen.set(revision.origin, true)
    if revision.before.origin != revision.origin ||
      revision.after.origin != revision.origin {
      delegation_finding(
        findings,
        "M003",
        "error",
        revision.origin,
        revision.origin,
        0,
        "declared origin differs from one of its parsed zone snapshots",
      )
    }
    let diff = compare_zones(revision.before, revision.after)
    for item in diff.diagnostics {
      delegation_finding(
        findings,
        item.code,
        item.severity,
        revision.origin + "-after",
        item.owner,
        item.line,
        item.message,
      )
    }
  }
  findings
}

///|
/// For each zone, choose the closest supplied ancestor. An ancestor can
/// serve several children; a child has at most one parent in this model.
fn nearest_supplied_parents(revisions : Array[ZoneRevision]) -> Array[Int] {
  let parents : Array[Int] = []
  for child_index in 0.. parent_length {
        parent_index = candidate
        parent_length = revisions[candidate].origin.length()
      }
    }
    parents.push(parent_index)
  }
  parents
}

///|
fn deployment_weights(count : Int) -> Array[Int] {
  let weights : Array[Int] = []
  let mut weight = 1
  for _ in 0.. Bool {
  mask / weight % 2 == 1
}

///|
fn deployment_zone(revision : ZoneRevision, updated : Bool) -> Zone {
  if updated {
    revision.after
  } else {
    revision.before
  }
}

///|
fn deployment_source(origin : String, updated : Bool) -> String {
  origin + (if updated { "-after" } else { "-before" })
}

///|
fn inspect_deployment_snapshot(
  mask : Int,
  revisions : Array[ZoneRevision],
  parents : Array[Int],
  weights : Array[Int],
) -> DeploymentSnapshot {
  let findings : Array[DelegationFinding] = []
  for i in 0.. Bool {
  for item in findings {
    if item.severity == "error" {
      return true
    }
  }
  false
}

///|
fn deployment_blocked_count(snapshots : Array[DeploymentSnapshot]) -> Int {
  let mut count = 0
  for snapshot in snapshots {
    if snapshot.blocked {
      count += 1
    }
  }
  count
}

///|
fn deployment_reconstruct_order(
  last_mask : Int,
  predecessors : Array[Int],
  weights : Array[Int],
  revisions : Array[ZoneRevision],
) -> Array[String] {
  let reversed : Array[String] = []
  let mut cursor = last_mask
  while cursor > 0 {
    let previous = predecessors[cursor]
    let changed_bit = cursor - previous
    for i in 0.. Int {
  let mut count = 0
  for weight in weights {
    if deployment_updated(mask, weight) {
      count += 1
    }
  }
  count
}

///|
fn deployment_counterexample(
  revisions : Array[ZoneRevision],
  weights : Array[Int],
  snapshots : Array[DeploymentSnapshot],
  predecessors : Array[Int],
) -> DeploymentCounterexample? {
  if snapshots[0].blocked {
    return Some({
      from_mask: -1,
      to_mask: 0,
      changed_zone: "",
      safe_prefix: [],
      findings: snapshots[0].findings,
    })
  }
  let mut best_depth = -1
  let mut best_from = -1
  let mut best_to = -1
  let mut best_zone = ""
  for mask in 0.. best_depth {
        best_depth = depth
        best_from = mask
        best_to = next
        best_zone = revisions[i].origin
      }
    }
  }
  if best_to < 0 {
    None
  } else {
    Some({
      from_mask: best_from,
      to_mask: best_to,
      changed_zone: best_zone,
      safe_prefix: deployment_reconstruct_order(
        best_from, predecessors, weights, revisions,
      ),
      findings: snapshots[best_to].findings,
    })
  }
}

///|
/// First publications that still permit at least one complete safe path.
fn deployment_first_steps(
  revisions : Array[ZoneRevision],
  weights : Array[Int],
  completions : Array[Int],
) -> Array[String] {
  let first : Array[String] = []
  if completions[0] == 0 {
    return first
  }
  for i in 0.. 0 {
      first.push(revisions[i].origin)
    }
  }
  first
}

///|
/// Dynamic programming over the state lattice counts complete safe orders.
/// With six zones the maximum is 6! = 720, within Int range.
fn deployment_completion_counts(
  snapshots : Array[DeploymentSnapshot],
  weights : Array[Int],
) -> Array[Int] {
  let total = snapshots.length()
  let counts : Array[Int] = []
  for _ in 0.. Array[DeploymentPrecedence] {
  let constraints : Array[DeploymentPrecedence] = []
  if completions[0] == 0 {
    return constraints
  }
  for a in 0..= 0 &&
          completions[mask] > 0 &&
          deployment_updated(mask, weights[b]) &&
          !deployment_updated(mask, weights[a]) {
          b_can_precede_a = true
          break
        }
      }
      if !b_can_precede_a {
        constraints.push({
          before_origin: revisions[a].origin,
          after_origin: revisions[b].origin,
        })
      }
    }
  }
  constraints
}

///|
/// Enumerate at most 64 before/after combinations, then search for a path
/// from all-before to all-after where one zone changes at each step.
pub fn plan_delegation_rollout(
  revisions : Array[ZoneRevision],
) -> DeploymentPlan {
  let zone_order : Array[String] = []
  for revision in revisions {
    zone_order.push(revision.origin)
  }
  let preflight = deployment_preflight(revisions)
  if revisions.length() < 2 || revisions.length() > 6 {
    return {
      status: "invalid-input",
      zone_order,
      order: [],
      snapshots: [],
      preflight,
      counterexample: None,
      safe_order_count: 0,
      required_precedence: [],
      first_steps: [],
      blocked_state_count: 0,
    }
  }
  let parents = nearest_supplied_parents(revisions)
  for child_index in 0..= 0 {
      audit_dnssec_transition(
        revisions[parent_index].before,
        revisions[parent_index].after,
        revisions[child_index].origin,
        revisions[parent_index].origin + "-after",
        preflight,
      )
    }
  }
  let mut edges = 0
  for parent in parents {
    if parent >= 0 {
      edges += 1
    }
  }
  if edges == 0 {
    delegation_finding(
      preflight, "M004", "error", "manifest", "", 0, "no parent-child relationship among supplied zone origins",
    )
  }
  let weights = deployment_weights(revisions.length())
  let total = weights[weights.length() - 1] * 2
  let snapshots : Array[DeploymentSnapshot] = []
  for mask in 0.. Unit {
  out.write_char('{')
  write_json_field(out, "code", item.code, false)
  write_json_field(out, "severity", item.severity, true)
  write_json_field(out, "source", item.source, true)
  write_json_field(out, "owner", item.owner, true)
  write_json_number(out, "line", item.line, true)
  write_json_field(out, "message", item.message, true)
  out.write_char('}')
}

///|
pub fn render_deployment_plan_json(plan : DeploymentPlan) -> String {
  let out = StringBuilder()
  out.write_char('{')
  write_json_field(out, "status", plan.status, false)
  out.write_string(",\"zone_order\":[")
  for i in 0.. 0 {
      out.write_char(',')
    }
    out.write_string(json_escaped(plan.zone_order[i]))
  }
  out.write_char(']')
  write_json_number(out, "safe_order_count", plan.safe_order_count, true)
  write_json_number(out, "blocked_state_count", plan.blocked_state_count, true)
  write_json_number(out, "examined_state_count", plan.snapshots.length(), true)
  write_json_number(
    out,
    "passing_state_count",
    plan.snapshots.length() - plan.blocked_state_count,
    true,
  )
  out.write_string(",\"first_steps\":[")
  for i in 0.. 0 {
      out.write_char(',')
    }
    out.write_string(json_escaped(plan.first_steps[i]))
  }
  out.write_char(']')
  out.write_string(",\"order\":[")
  for i in 0.. 0 {
      out.write_char(',')
    }
    out.write_string(json_escaped(plan.order[i]))
  }
  out.write_string("],\"required_precedence\":[")
  for i in 0.. 0 {
      out.write_char(',')
    }
    let constraint = plan.required_precedence[i]
    out.write_char('{')
    write_json_field(out, "before", constraint.before_origin, false)
    write_json_field(out, "after", constraint.after_origin, true)
    out.write_char('}')
  }
  out.write_string("],\"preflight\":[")
  for i in 0.. 0 {
      out.write_char(',')
    }
    write_deployment_finding_json(out, plan.preflight[i])
  }
  out.write_string("],\"snapshots\":[")
  for i in 0.. 0 {
      out.write_char(',')
    }
    let snapshot = plan.snapshots[i]
    out.write_string("{\"mask\":")
    out.write_string(snapshot.mask.to_string())
    out.write_string(",\"updated_zones\":[")
    let mut weight = 1
    let mut written = false
    for origin in plan.zone_order {
      if deployment_updated(snapshot.mask, weight) {
        if written {
          out.write_char(',')
        }
        out.write_string(json_escaped(origin))
        written = true
      }
      weight *= 2
    }
    out.write_char(']')
    out.write_string(",\"blocked\":")
    out.write_string(if snapshot.blocked { "true" } else { "false" })
    out.write_string(",\"findings\":[")
    for j in 0.. 0 {
        out.write_char(',')
      }
      write_deployment_finding_json(out, snapshot.findings[j])
    }
    out.write_string("]}")
  }
  out.write_string("],\"counterexample\":")
  match plan.counterexample {
    None => out.write_string("null")
    Some(witness) => {
      out.write_string("{\"from_mask\":")
      out.write_string(witness.from_mask.to_string())
      out.write_string(",\"to_mask\":")
      out.write_string(witness.to_mask.to_string())
      write_json_field(out, "changed_zone", witness.changed_zone, true)
      out.write_string(",\"safe_prefix\":[")
      for i in 0.. 0 {
          out.write_char(',')
        }
        out.write_string(json_escaped(witness.safe_prefix[i]))
      }
      out.write_char(']')
      out.write_string(",\"findings\":[")
      for i in 0.. 0 {
          out.write_char(',')
        }
        write_deployment_finding_json(out, witness.findings[i])
      }
      out.write_string("]}")
    }
  }
  out.write_char('}')
  out.to_string()
}

///|
pub fn render_deployment_plan_text(plan : DeploymentPlan) -> String {
  let out = StringBuilder()
  out.write_string("status: ")
  out.write_string(plan.status)
  out.write_char('\n')
  out.write_string("safe orders: ")
  out.write_string(plan.safe_order_count.to_string())
  out.write_char('\n')
  out.write_string("blocked states: ")
  out.write_string(plan.blocked_state_count.to_string())
  out.write_char('\n')
  out.write_string("examined states: ")
  out.write_string(plan.snapshots.length().to_string())
  out.write_char('\n')
  if plan.first_steps.length() > 0 {
    out.write_string("viable first updates:")
    for origin in plan.first_steps {
      out.write_char(' ')
      out.write_string(origin)
    }
    out.write_char('\n')
  }
  if plan.order.length() > 0 {
    out.write_string("order:")
    for origin in plan.order {
      out.write_char(' ')
      out.write_string(origin)
    }
    out.write_char('\n')
  }
  for constraint in plan.required_precedence {
    out.write_string("must precede: ")
    out.write_string(constraint.before_origin)
    out.write_string(" -> ")
    out.write_string(constraint.after_origin)
    out.write_char('\n')
  }
  match plan.counterexample {
    Some(witness) => {
      if witness.safe_prefix.length() > 0 {
        out.write_string("counterexample safe prefix:")
        for origin in witness.safe_prefix {
          out.write_char(' ')
          out.write_string(origin)
        }
        out.write_char('\n')
      }
      out.write_string("counterexample: state ")
      out.write_string(witness.from_mask.to_string())
      out.write_string(" -> state ")
      out.write_string(witness.to_mask.to_string())
      if witness.changed_zone != "" {
        out.write_string(" after publishing ")
        out.write_string(witness.changed_zone)
      }
      out.write_char('\n')
    }
    None => ()
  }
  for item in plan.preflight {
    out.write_string("preflight ")
    out.write_string(item.severity)
    out.write_char(' ')
    out.write_string(item.code)
    out.write_string(" [")
    out.write_string(item.source)
    out.write_string("] ")
    out.write_string(item.message)
    out.write_char('\n')
  }
  for snapshot in plan.snapshots {
    out.write_string("state ")
    out.write_string(snapshot.mask.to_string())
    out.write_string(if snapshot.blocked { ": blocked\n" } else { ": pass\n" })
    if snapshot.blocked {
      for item in snapshot.findings {
        if item.severity == "error" {
          out.write_string("  ")
          out.write_string(item.code)
          out.write_string(" [")
          out.write_string(item.source)
          out.write_char(':')
          out.write_string(item.line.to_string())
          out.write_string("] ")
          out.write_string(item.owner)
          out.write_string(": ")
          out.write_string(item.message)
          out.write_char('\n')
        }
      }
    }
  }
  out.to_string()
}