///|
fn require(
  result : Result[@sketch.Solution, @sketch.SketchError],
  satisfied : Bool,
) -> @sketch.Solution {
  let r = match result {
    Ok(r) => r
    Err(e) => abort(e.message())
  }
  if r.report.is_satisfied() != satisfied {
    abort("Unexpected convergence state in editing example")
  }
  if satisfied && r.report.local_dof != 0 {
    abort("Editing example should be fully constrained")
  }
  r
}

///|
fn check_coordinate(value : Double, expected : Double) -> Unit {
  if (value - expected).abs() > 1.0e-5 {
    abort("Unexpected coordinate: \{value}; expected \{expected}")
  }
}

///|
fn main {
  let s = @sketch.Sketch::new()
  let origin = s.add_point(0.0, 0.0)
  let left = s.add_point(-4.4, 2.1)
  let right = s.add_point(4.5, -1.8)
  let a = s.add_point(-10.0, -5.0)
  let b = s.add_point(10.0, -5.0)
  let center = s.add_point(3.0, 3.8)
  let baseline = s.add_line(a, b)
  let spoke = s.add_line(origin, left)
  let outer = s.add_circle(origin, 7.0)
  let inner = s.add_circle(center, 2.0)
  s.constrain(Fix(origin, 0.0, 0.0)) |> ignore
  s.constrain(Fix(a, -10.0, -5.0)) |> ignore
  s.constrain(Fix(b, 10.0, -5.0)) |> ignore
  s.constrain(Symmetric(left, right, origin)) |> ignore
  let offset = s.constrain(DistanceToLine(left, baseline, 7.0))
  let ratio = s.constrain(LengthRatio(spoke, baseline, 0.25))
  s.constrain(Radius(outer, 7.0)) |> ignore
  s.constrain(Radius(inner, 2.0)) |> ignore
  s.constrain(InternalTangent(outer, inner)) |> ignore
  s.constrain(DistanceX(origin, center, 3.0)) |> ignore
  let conflict = s.constrain(Fix(left, 9.0, 9.0))
  s.set_constraint_enabled(conflict, false) |> ignore
  let first = require(s.solve_and_apply(), true)
  let p = first.point(left).unwrap()
  check_coordinate(p.x, -21.0.sqrt())
  check_coordinate(p.y, 2.0)
  check_coordinate(first.point(right).unwrap().x, 21.0.sqrt())
  check_coordinate(first.point(center).unwrap().y, 4.0)
  println(
    "Initial sketch: satisfied; symmetry, offset, ratio, and internal tangency verified",
  )

  s.set_constraint_enabled(conflict, true) |> ignore
  let failed = require(s.solve_and_apply(), false)
  let retained = s.point(left).unwrap()
  if retained.x != p.x || retained.y != p.y {
    abort("Failed editing solve changed the sketch")
  }
  for finding in failed.report.unsatisfied() {
    if s.constraint(finding.id) is None {
      abort("Missing diagnostic relation")
    }
  }
  println("Conflicting edit: rejected; previous geometry retained")

  s.set_constraint_enabled(conflict, false) |> ignore
  s.set_constraint(offset, DistanceToLine(left, baseline, 6.0)) |> ignore
  s.set_constraint(ratio, LengthRatio(spoke, baseline, 0.3)) |> ignore
  s.set_radius(outer, 8.0) |> ignore
  let revised = require(s.solve_and_apply(), true)
  let p = revised.point(left).unwrap()
  check_coordinate(p.x, -35.0.sqrt())
  check_coordinate(p.y, 1.0)
  check_coordinate(revised.point(right).unwrap().y, -1.0)
  check_coordinate(revised.radius(outer).unwrap(), 7.0)
  println(
    "Revised dimensions: satisfied; left=(\{p.x}, \{p.y}); original constraint IDs retained",
  )
}