///|
/// Failure reason for result-typed topology queue APIs.
pub(all) enum GraphTopologyEditError {
  InvalidNodeIndex(Int)
  InvalidSourceIndex(Int)
  UnsupportedInputSlot(Int, GraphTopologyInputSlot, DspNodeKind)
  UnsupportedInsertTemplate(DspNodeKind)
  EmptyInsertChain
  UnsupportedChainNode(Int, DspNodeKind)
  InvalidDeleteRange(Int, Int)
  ReplacementSourceInDeletedRange(Int)
  DeleteNodeRequiresUnary(Int, DspNodeKind)
  DeleteNodeRequiresSingleConsumer(Int)
  DeleteNodeConsumerMismatch(Int, Int)
  DeleteChainRequiresUnary(Int, DspNodeKind)
  DeleteChainRequiresSingleConsumer(Int)
  DeleteChainConsumerMismatch(Int, Int, Int)
} derive(Debug)

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

///|
/// Failure reason for result-typed topology queue APIs.
pub(all) enum GraphTopologyQueueError {
  PendingSwap
  InvalidEdit(Int, GraphTopologyEditError)
  RecompileRejected
  HotSwap(HotSwapQueueError)
} derive(Debug)

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

///|
fn graph_topology_edit_error_from_staging(
  error : @staging.GraphTopologyEditError,
) -> GraphTopologyEditError {
  match error {
    @staging.GraphTopologyEditError::InvalidNodeIndex(index) =>
      GraphTopologyEditError::InvalidNodeIndex(index)
    @staging.GraphTopologyEditError::InvalidSourceIndex(index) =>
      GraphTopologyEditError::InvalidSourceIndex(index)
    @staging.GraphTopologyEditError::UnsupportedInputSlot(index, slot, kind) =>
      GraphTopologyEditError::UnsupportedInputSlot(
        index,
        graph_topology_input_slot_from_staging(slot),
        dsp_node_kind_from_model(kind),
      )
    @staging.GraphTopologyEditError::UnsupportedInsertTemplate(kind) =>
      GraphTopologyEditError::UnsupportedInsertTemplate(
        dsp_node_kind_from_model(kind),
      )
    @staging.GraphTopologyEditError::EmptyInsertChain =>
      GraphTopologyEditError::EmptyInsertChain
    @staging.GraphTopologyEditError::UnsupportedChainNode(index, kind) =>
      GraphTopologyEditError::UnsupportedChainNode(
        index,
        dsp_node_kind_from_model(kind),
      )
    @staging.GraphTopologyEditError::InvalidDeleteRange(start_index, end_index) =>
      GraphTopologyEditError::InvalidDeleteRange(start_index, end_index)
    @staging.GraphTopologyEditError::ReplacementSourceInDeletedRange(index) =>
      GraphTopologyEditError::ReplacementSourceInDeletedRange(index)
    @staging.GraphTopologyEditError::DeleteNodeRequiresUnary(index, kind) =>
      GraphTopologyEditError::DeleteNodeRequiresUnary(
        index,
        dsp_node_kind_from_model(kind),
      )
    @staging.GraphTopologyEditError::DeleteNodeRequiresSingleConsumer(index) =>
      GraphTopologyEditError::DeleteNodeRequiresSingleConsumer(index)
    @staging.GraphTopologyEditError::DeleteNodeConsumerMismatch(
      consumer,
      retarget
    ) => GraphTopologyEditError::DeleteNodeConsumerMismatch(consumer, retarget)
    @staging.GraphTopologyEditError::DeleteChainRequiresUnary(index, kind) =>
      GraphTopologyEditError::DeleteChainRequiresUnary(
        index,
        dsp_node_kind_from_model(kind),
      )
    @staging.GraphTopologyEditError::DeleteChainRequiresSingleConsumer(index) =>
      GraphTopologyEditError::DeleteChainRequiresSingleConsumer(index)
    @staging.GraphTopologyEditError::DeleteChainConsumerMismatch(
      index,
      consumer,
      expected
    ) =>
      GraphTopologyEditError::DeleteChainConsumerMismatch(
        index, consumer, expected,
      )
  }
}

///|
fn graph_topology_queue_error_from_staging(
  error : @staging.GraphTopologyQueueError,
) -> GraphTopologyQueueError {
  match error {
    @staging.GraphTopologyQueueError::PendingSwap =>
      GraphTopologyQueueError::PendingSwap
    @staging.GraphTopologyQueueError::InvalidEdit(index, reason) =>
      GraphTopologyQueueError::InvalidEdit(
        index,
        graph_topology_edit_error_from_staging(reason),
      )
    @staging.GraphTopologyQueueError::RecompileRejected =>
      GraphTopologyQueueError::RecompileRejected
    @staging.GraphTopologyQueueError::HotSwap(error) =>
      GraphTopologyQueueError::HotSwap(hot_swap_queue_error_from_staging(error))
  }
}

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

///|
fn graph_topology_edits_to_staging(
  edits : Array[GraphTopologyEdit],
) -> Array[@staging.GraphTopologyEdit] {
  let raw_edits = Array(capacity=edits.length())
  for edit in edits {
    raw_edits.push(edit.to_staging())
  }
  raw_edits
}

///|
/// Mono topology-edit wrapper that recompiles authoring nodes and stages the
/// replacement through the internal hot-swap graph.
///
/// Public facade wrapper; implementation lives in `graph/internal/staging`.
struct CompiledDspTopologyController(@staging.CompiledDspTopologyController)

///|
/// Build a topology-edit wrapper from authoring-order mono nodes.
pub fn CompiledDspTopologyController::from_nodes(
  nodes : Array[DspNode],
  context : DspContext,
  crossfade_samples? : Int = 0,
) -> CompiledDspTopologyController? {
  @staging.CompiledDspTopologyController::from_nodes(
    dsp_nodes_to_model(nodes),
    context,
    crossfade_samples~,
  ).map(fn(raw) { CompiledDspTopologyController(raw) })
}

///|
/// Queue one topology edit for the next hot-swap recompilation.
pub fn CompiledDspTopologyController::queue_topology_edit(
  self : CompiledDspTopologyController,
  edit : GraphTopologyEdit,
) -> Result[Unit, GraphTopologyQueueError] {
  graph_topology_queue_result_from_staging(
    self.0.queue_topology_edit(edit.to_staging()),
  )
}

///|
/// Queue an ordered topology-edit batch.
///
/// The batch is transactional: invalid node indices or a recompilation failure
/// reject the whole edit set and leave the current graph unchanged.
pub fn CompiledDspTopologyController::queue_topology_edits(
  self : CompiledDspTopologyController,
  edits : Array[GraphTopologyEdit],
) -> Result[Unit, GraphTopologyQueueError] {
  graph_topology_queue_result_from_staging(
    self.0.queue_topology_edits(graph_topology_edits_to_staging(edits)),
  )
}

///|
/// Process one block through the active or crossfading mono graph.
pub fn CompiledDspTopologyController::process(
  self : CompiledDspTopologyController,
  context : DspContext,
  output : AudioBuffer,
) -> Unit {
  self.0.process(context, output)
}

///|
/// Terminal-stereo topology-edit wrapper that recompiles authoring nodes and
/// stages the replacement through the internal hot-swap graph.
///
/// Public facade wrapper; implementation lives in `graph/internal/staging`.
struct CompiledStereoDspTopologyController(
  @staging.CompiledStereoDspTopologyController
)

///|
/// Build a topology-edit wrapper from authoring-order terminal-stereo nodes.
pub fn CompiledStereoDspTopologyController::from_nodes(
  nodes : Array[DspNode],
  context : DspContext,
  crossfade_samples? : Int = 0,
) -> CompiledStereoDspTopologyController? {
  @staging.CompiledStereoDspTopologyController::from_nodes(
    dsp_nodes_to_model(nodes),
    context,
    crossfade_samples~,
  ).map(fn(raw) { CompiledStereoDspTopologyController(raw) })
}

///|
/// Queue one topology edit for the next stereo hot-swap recompilation.
pub fn CompiledStereoDspTopologyController::queue_topology_edit(
  self : CompiledStereoDspTopologyController,
  edit : GraphTopologyEdit,
) -> Result[Unit, GraphTopologyQueueError] {
  graph_topology_queue_result_from_staging(
    self.0.queue_topology_edit(edit.to_staging()),
  )
}

///|
/// Queue an ordered stereo topology-edit batch.
///
/// The batch is transactional: invalid node indices or a recompilation failure
/// reject the whole edit set and leave the current graph unchanged.
pub fn CompiledStereoDspTopologyController::queue_topology_edits(
  self : CompiledStereoDspTopologyController,
  edits : Array[GraphTopologyEdit],
) -> Result[Unit, GraphTopologyQueueError] {
  graph_topology_queue_result_from_staging(
    self.0.queue_topology_edits(graph_topology_edits_to_staging(edits)),
  )
}

///|
/// Process one block through the active or crossfading stereo graph.
pub fn CompiledStereoDspTopologyController::process(
  self : CompiledStereoDspTopologyController,
  context : DspContext,
  left_output : AudioBuffer,
  right_output : AudioBuffer,
) -> Unit {
  self.0.process(context, left_output, right_output)
}