///|
// OCCT-backed evaluated export of nominal booleans.
//
// `to_csg_step` already emits a real boolean tree (`CSG_SOLID` wrapping
// `BOOLEAN_RESULT` nodes), but OCCT's STEP reader cannot consume that tree.
// `scripts/csg_eval.py` loads each leaf B-Rep, folds the tree with
// `bfuse`/`bcut`/`bcommon`, and writes a flat, trimmed B-Rep. This module
// wires that development-time tool into the async export flow.
//
// The library itself stays kernel-free: the Python script and OCCT are
// optional runtime tools. When either is missing the evaluated export fails
// with an explicit message; the nominal STEP paths are unchanged.

///|
/// CsgEvalError reports why an evaluated CSG export could not be produced.
pub suberror CsgEvalError {
  CsgEvalError(String)
}

///|
/// csg_eval_script_path joins a repository root with `scripts/csg_eval.py`.
///
/// A `None` root falls back to the current-directory relative path, matching
/// the Blender exporter's script resolution.
pub fn csg_eval_script_path(root : String?) -> String {
  match root {
    Some(r) =>
      if r.has_suffix("/") {
        r + "scripts/csg_eval.py"
      } else {
        r + "/scripts/csg_eval.py"
      }
    None => "./scripts/csg_eval.py"
  }
}

///|
/// find_csg_eval_script resolves `$MOONBIT_STEP_ROOT` (falling back to
/// `./scripts/csg_eval.py`) and returns the path only when it exists.
pub async fn find_csg_eval_script(root : String?) -> String? {
  let path = csg_eval_script_path(root)
  if @fs.exists(path) {
    Some(path)
  } else {
    None
  }
}

///|
/// Design::to_step_evaluated evaluates the design's boolean tree with OCCT and
/// returns the resulting flat, trimmed B-Rep STEP document.
///
/// It writes `to_csg_step()` to a temporary file, runs
/// `python3 scripts/csg_eval.py` with `$MOONBIT_STEP_ROOT` (or `root`), and
/// reads back the evaluated STEP. Raises `CsgEvalError` when the script is
/// missing or OCCT fails.
pub async fn Design::to_step_evaluated(
  self : Design,
  root~ : String? = None,
) -> String {
  let root = match root {
    Some(_) => root
    None => @env.get_env_var("MOONBIT_STEP_ROOT")
  }
  let script = match find_csg_eval_script(root) {
    Some(path) => path
    None =>
      raise CsgEvalError(
        "cannot evaluate CSG booleans: '\{csg_eval_script_path(root)}' not found. Set MOONBIT_STEP_ROOT to the repository root (or run from it). OCCT's occt-draw or DRAWEXE is also required.",
      )
  }
  let temp_dir = @fs.tmpdir(prefix="moonbit-csg-eval-")
  defer @fs.rmdir(temp_dir, recursive=true)
  let input_path = temp_dir + "/input.step"
  let output_path = temp_dir + "/evaluated.step"
  write_text_file(self.to_csg_step(), Some(input_path))
  // Capture the evaluator's output instead of inheriting stdout: callers may
  // be tests that talk a structured protocol on stdout.
  let (code, output) = @process.collect_output_merged("python3", [
    script, input_path, "-o", output_path,
  ])
  if code != 0 {
    raise CsgEvalError(
      "CSG evaluation failed (scripts/csg_eval.py exited with status \{code}): \{output.text()}. Evaluating booleans requires Open CASCADE ('occt-draw' or 'DRAWEXE' on PATH) and a design with a boolean tree.",
    )
  }
  if !@fs.exists(output_path) {
    raise CsgEvalError(
      "CSG evaluation finished but wrote no output to '\{output_path}'.",
    )
  }
  @fs.read_file(output_path).text()
}

///|
/// Design::write_step_evaluated writes the OCCT-evaluated STEP document to
/// `out_path` (or stdout). Aborts with the evaluator's message on failure.
pub async fn Design::write_step_evaluated(
  self : Design,
  out_path : String?,
  root~ : String? = None,
) -> Unit {
  let text = self.to_step_evaluated(root~) catch {
    CsgEvalError(msg) => abort(msg)
    err => abort("CSG evaluation failed: \{err}")
  }
  write_text_file(text, out_path)
}