///|
/// Failure reason for runtime graph control APIs.
pub(all) enum GraphControlError {
  InvalidNodeIndex(Int)
  /// The authoring node existed but was eliminated by graph optimization.
  OrphanNode(Int)
  InvalidGateNode(Int, DspNodeKind)
  InvalidSlotForNode(Int, DspNodeKind, GraphParamSlot)
  InvalidParamValue(Int, DspNodeKind, GraphParamSlot, Double)
  MissingRuntimeState(Int, DspNodeKind)
} derive(Debug)

///|
pub impl Show for GraphControlError with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
fn graph_control_error_from_runtime(
  error : @runtime.GraphControlError,
) -> GraphControlError {
  match error {
    @runtime.GraphControlError::InvalidNodeIndex(index) =>
      GraphControlError::InvalidNodeIndex(index)
    @runtime.GraphControlError::OrphanNode(index) =>
      GraphControlError::OrphanNode(index)
    @runtime.GraphControlError::InvalidGateNode(index, kind) =>
      GraphControlError::InvalidGateNode(index, dsp_node_kind_from_model(kind))
    @runtime.GraphControlError::InvalidSlotForNode(index, kind, slot) =>
      GraphControlError::InvalidSlotForNode(
        index,
        dsp_node_kind_from_model(kind),
        graph_param_slot_from_model(slot),
      )
    @runtime.GraphControlError::InvalidParamValue(index, kind, slot, value) =>
      GraphControlError::InvalidParamValue(
        index,
        dsp_node_kind_from_model(kind),
        graph_param_slot_from_model(slot),
        value,
      )
    @runtime.GraphControlError::MissingRuntimeState(index, kind) =>
      GraphControlError::MissingRuntimeState(
        index,
        dsp_node_kind_from_model(kind),
      )
  }
}

///|
fn graph_control_result_from_runtime(
  result : Result[Unit, @runtime.GraphControlError],
) -> Result[Unit, GraphControlError] {
  match result {
    Ok(_) => Ok(())
    Err(error) => Err(graph_control_error_from_runtime(error))
  }
}

///|
fn graph_control_error_from_staging(
  error : @staging.GraphControlError,
) -> GraphControlError {
  match error {
    @staging.GraphControlError::InvalidNodeIndex(index) =>
      GraphControlError::InvalidNodeIndex(index)
    @staging.GraphControlError::OrphanNode(index) =>
      GraphControlError::OrphanNode(index)
    @staging.GraphControlError::InvalidGateNode(index, kind) =>
      GraphControlError::InvalidGateNode(index, dsp_node_kind_from_model(kind))
    @staging.GraphControlError::InvalidSlotForNode(index, kind, slot) =>
      GraphControlError::InvalidSlotForNode(
        index,
        dsp_node_kind_from_model(kind),
        graph_param_slot_from_model(slot),
      )
    @staging.GraphControlError::InvalidParamValue(index, kind, slot, value) =>
      GraphControlError::InvalidParamValue(
        index,
        dsp_node_kind_from_model(kind),
        graph_param_slot_from_model(slot),
        value,
      )
    @staging.GraphControlError::MissingRuntimeState(index, kind) =>
      GraphControlError::MissingRuntimeState(
        index,
        dsp_node_kind_from_model(kind),
      )
  }
}

///|
fn graph_control_result_from_staging(
  result : Result[Unit, @staging.GraphControlError],
) -> Result[Unit, GraphControlError] {
  match result {
    Ok(_) => Ok(())
    Err(error) => Err(graph_control_error_from_staging(error))
  }
}

///|
/// Trigger `gate_on()` on an ADSR node using its original authoring index.
pub fn CompiledDsp::gate_on(
  self : CompiledDsp,
  node_index : Int,
) -> Result[Unit, GraphControlError] {
  self.apply_control(GraphControl::gate_on(node_index))
}

///|
/// Trigger `gate_off()` on an ADSR node using its original authoring index.
pub fn CompiledDsp::gate_off(
  self : CompiledDsp,
  node_index : Int,
) -> Result[Unit, GraphControlError] {
  self.apply_control(GraphControl::gate_off(node_index))
}

///|
/// Update a runtime parameter on a compiled node using its original authoring
/// index.
pub fn CompiledDsp::set_param(
  self : CompiledDsp,
  node_index : Int,
  slot : GraphParamSlot,
  value : Double,
) -> Result[Unit, GraphControlError] {
  self.apply_control(GraphControl::set_param(node_index, slot, value))
}

///|
/// Apply one runtime control message, returning a specific rejection reason.
pub fn CompiledDsp::apply_control(
  self : CompiledDsp,
  control : GraphControl,
) -> Result[Unit, GraphControlError] {
  graph_control_result_from_runtime(self.raw().apply_control(control.raw()))
}

///|
/// Apply a runtime control batch transactionally.
pub fn CompiledDsp::apply_controls(
  self : CompiledDsp,
  controls : Array[GraphControl],
) -> Result[Unit, GraphControlError] {
  graph_control_result_from_runtime(
    self.raw().apply_controls(graph_controls_to_model(controls)),
  )
}

///|
/// Validate a runtime control batch without mutating the compiled graph.
pub fn CompiledDsp::validate_controls(
  self : CompiledDsp,
  controls : Array[GraphControl],
) -> Result[Unit, GraphControlError] {
  graph_control_result_from_runtime(
    self.raw().validate_controls(graph_controls_to_model(controls)),
  )
}

///|
/// Allocation-conscious gate-on primitive for prevalidated host paths.
pub fn CompiledDsp::gate_on_bool(self : CompiledDsp, node_index : Int) -> Bool {
  self.raw().gate_on_bool(node_index)
}

///|
/// Allocation-conscious gate-off primitive for prevalidated host paths.
pub fn CompiledDsp::gate_off_bool(self : CompiledDsp, node_index : Int) -> Bool {
  self.raw().gate_off_bool(node_index)
}

///|
/// Allocation-conscious parameter primitive for prevalidated host paths.
pub fn CompiledDsp::set_param_bool(
  self : CompiledDsp,
  node_index : Int,
  slot : GraphParamSlot,
  value : Double,
) -> Bool {
  self.raw().set_param_bool(node_index, slot.to_model(), value)
}

///|
/// Allocation-conscious parameter validation for prevalidated host paths.
pub fn CompiledDsp::can_set_param_bool(
  self : CompiledDsp,
  node_index : Int,
  slot : GraphParamSlot,
  value : Double,
) -> Bool {
  self.raw().can_set_param_bool(node_index, slot.to_model(), value)
}

///|
/// Returns true when a voice using this compiled graph can be safely reclaimed.
///
/// Two-stage check:
/// 1. All ADSR nodes must be in Idle stage (envelope has finished)
/// 2. The last output buffer must be silent (all samples below threshold)
///
/// WHY two stages: ADSR-only detection would cut voices with downstream delay
/// or feedback tails that are still audible. Energy-only detection would keep
/// voices alive during sustain (where output is non-zero but expected).
pub fn CompiledDsp::is_voice_finished(self : CompiledDsp) -> Bool {
  self.raw().is_voice_finished()
}

///|
/// Reset mutable runtime state and restore compile-time node parameters.
pub fn CompiledDsp::reset_runtime_state(self : CompiledDsp) -> Unit {
  self.raw().reset_runtime_state()
}

///|
/// Trigger `gate_on()` on a stereo-graph ADSR node using its original
/// authoring index.
pub fn CompiledStereoDsp::gate_on(
  self : CompiledStereoDsp,
  node_index : Int,
) -> Result[Unit, GraphControlError] {
  self.apply_control(GraphControl::gate_on(node_index))
}

///|
/// Trigger `gate_off()` on a stereo-graph ADSR node using its original
/// authoring index.
pub fn CompiledStereoDsp::gate_off(
  self : CompiledStereoDsp,
  node_index : Int,
) -> Result[Unit, GraphControlError] {
  self.apply_control(GraphControl::gate_off(node_index))
}

///|
/// Update a runtime parameter on a compiled stereo node using its original
/// authoring index.
pub fn CompiledStereoDsp::set_param(
  self : CompiledStereoDsp,
  node_index : Int,
  slot : GraphParamSlot,
  value : Double,
) -> Result[Unit, GraphControlError] {
  self.apply_control(GraphControl::set_param(node_index, slot, value))
}

///|
/// Apply one runtime control message, returning a specific rejection reason.
pub fn CompiledStereoDsp::apply_control(
  self : CompiledStereoDsp,
  control : GraphControl,
) -> Result[Unit, GraphControlError] {
  graph_control_result_from_runtime(self.raw().apply_control(control.raw()))
}

///|
/// Apply a runtime control batch transactionally.
pub fn CompiledStereoDsp::apply_controls(
  self : CompiledStereoDsp,
  controls : Array[GraphControl],
) -> Result[Unit, GraphControlError] {
  graph_control_result_from_runtime(
    self.raw().apply_controls(graph_controls_to_model(controls)),
  )
}

///|
/// Validate a runtime control batch without mutating the compiled graph.
pub fn CompiledStereoDsp::validate_controls(
  self : CompiledStereoDsp,
  controls : Array[GraphControl],
) -> Result[Unit, GraphControlError] {
  graph_control_result_from_runtime(
    self.raw().validate_controls(graph_controls_to_model(controls)),
  )
}

///|
/// Trigger runtime gate-on through a mono hot-swap wrapper.
pub fn CompiledDspHotSwap::gate_on(
  self : CompiledDspHotSwap,
  node_index : Int,
) -> Result[Unit, GraphControlError] {
  self.apply_control(GraphControl::gate_on(node_index))
}

///|
/// Trigger runtime gate-off through a mono hot-swap wrapper.
pub fn CompiledDspHotSwap::gate_off(
  self : CompiledDspHotSwap,
  node_index : Int,
) -> Result[Unit, GraphControlError] {
  self.apply_control(GraphControl::gate_off(node_index))
}

///|
/// Update a runtime parameter through a mono hot-swap wrapper.
pub fn CompiledDspHotSwap::set_param(
  self : CompiledDspHotSwap,
  node_index : Int,
  slot : GraphParamSlot,
  value : Double,
) -> Result[Unit, GraphControlError] {
  self.apply_control(GraphControl::set_param(node_index, slot, value))
}

///|
/// Apply one runtime control message through a mono hot-swap wrapper.
pub fn CompiledDspHotSwap::apply_control(
  self : CompiledDspHotSwap,
  control : GraphControl,
) -> Result[Unit, GraphControlError] {
  graph_control_result_from_staging(self.0.apply_control(control.raw()))
}

///|
/// Apply a runtime control batch transactionally through a mono hot-swap wrapper.
pub fn CompiledDspHotSwap::apply_controls(
  self : CompiledDspHotSwap,
  controls : Array[GraphControl],
) -> Result[Unit, GraphControlError] {
  graph_control_result_from_staging(
    self.0.apply_controls(graph_controls_to_model(controls)),
  )
}

///|
/// Trigger runtime gate-on through a stereo hot-swap wrapper.
pub fn CompiledStereoDspHotSwap::gate_on(
  self : CompiledStereoDspHotSwap,
  node_index : Int,
) -> Result[Unit, GraphControlError] {
  self.apply_control(GraphControl::gate_on(node_index))
}

///|
/// Trigger runtime gate-off through a stereo hot-swap wrapper.
pub fn CompiledStereoDspHotSwap::gate_off(
  self : CompiledStereoDspHotSwap,
  node_index : Int,
) -> Result[Unit, GraphControlError] {
  self.apply_control(GraphControl::gate_off(node_index))
}

///|
/// Update a runtime parameter through a stereo hot-swap wrapper.
pub fn CompiledStereoDspHotSwap::set_param(
  self : CompiledStereoDspHotSwap,
  node_index : Int,
  slot : GraphParamSlot,
  value : Double,
) -> Result[Unit, GraphControlError] {
  self.apply_control(GraphControl::set_param(node_index, slot, value))
}

///|
/// Apply one runtime control message through a stereo hot-swap wrapper.
pub fn CompiledStereoDspHotSwap::apply_control(
  self : CompiledStereoDspHotSwap,
  control : GraphControl,
) -> Result[Unit, GraphControlError] {
  graph_control_result_from_staging(self.0.apply_control(control.raw()))
}

///|
/// Apply a runtime control batch transactionally through a stereo hot-swap wrapper.
pub fn CompiledStereoDspHotSwap::apply_controls(
  self : CompiledStereoDspHotSwap,
  controls : Array[GraphControl],
) -> Result[Unit, GraphControlError] {
  graph_control_result_from_staging(
    self.0.apply_controls(graph_controls_to_model(controls)),
  )
}

///|
/// Trigger runtime gate-on through a mono topology controller.
pub fn CompiledDspTopologyController::gate_on(
  self : CompiledDspTopologyController,
  node_index : Int,
) -> Result[Unit, GraphControlError] {
  self.apply_control(GraphControl::gate_on(node_index))
}

///|
/// Trigger runtime gate-off through a mono topology controller.
pub fn CompiledDspTopologyController::gate_off(
  self : CompiledDspTopologyController,
  node_index : Int,
) -> Result[Unit, GraphControlError] {
  self.apply_control(GraphControl::gate_off(node_index))
}

///|
/// Update a runtime parameter through a mono topology controller.
pub fn CompiledDspTopologyController::set_param(
  self : CompiledDspTopologyController,
  node_index : Int,
  slot : GraphParamSlot,
  value : Double,
) -> Result[Unit, GraphControlError] {
  self.apply_control(GraphControl::set_param(node_index, slot, value))
}

///|
/// Apply one runtime control message through a mono topology controller.
pub fn CompiledDspTopologyController::apply_control(
  self : CompiledDspTopologyController,
  control : GraphControl,
) -> Result[Unit, GraphControlError] {
  graph_control_result_from_staging(self.0.apply_control(control.raw()))
}

///|
/// Apply a runtime control batch transactionally through a mono topology controller.
pub fn CompiledDspTopologyController::apply_controls(
  self : CompiledDspTopologyController,
  controls : Array[GraphControl],
) -> Result[Unit, GraphControlError] {
  graph_control_result_from_staging(
    self.0.apply_controls(graph_controls_to_model(controls)),
  )
}

///|
/// Trigger runtime gate-on through a stereo topology controller.
pub fn CompiledStereoDspTopologyController::gate_on(
  self : CompiledStereoDspTopologyController,
  node_index : Int,
) -> Result[Unit, GraphControlError] {
  self.apply_control(GraphControl::gate_on(node_index))
}

///|
/// Trigger runtime gate-off through a stereo topology controller.
pub fn CompiledStereoDspTopologyController::gate_off(
  self : CompiledStereoDspTopologyController,
  node_index : Int,
) -> Result[Unit, GraphControlError] {
  self.apply_control(GraphControl::gate_off(node_index))
}

///|
/// Update a runtime parameter through a stereo topology controller.
pub fn CompiledStereoDspTopologyController::set_param(
  self : CompiledStereoDspTopologyController,
  node_index : Int,
  slot : GraphParamSlot,
  value : Double,
) -> Result[Unit, GraphControlError] {
  self.apply_control(GraphControl::set_param(node_index, slot, value))
}

///|
/// Apply one runtime control message through a stereo topology controller.
pub fn CompiledStereoDspTopologyController::apply_control(
  self : CompiledStereoDspTopologyController,
  control : GraphControl,
) -> Result[Unit, GraphControlError] {
  graph_control_result_from_staging(self.0.apply_control(control.raw()))
}

///|
/// Apply a runtime control batch transactionally through a stereo topology
/// controller.
pub fn CompiledStereoDspTopologyController::apply_controls(
  self : CompiledStereoDspTopologyController,
  controls : Array[GraphControl],
) -> Result[Unit, GraphControlError] {
  graph_control_result_from_staging(
    self.0.apply_controls(graph_controls_to_model(controls)),
  )
}

///|
fn graph_controls_to_model(
  controls : Array[GraphControl],
) -> Array[@model.GraphControl] {
  let raw_controls = Array(capacity=controls.length())
  for control in controls {
    raw_controls.push(control.raw())
  }
  raw_controls
}