///|
/// Minimal Phase 2 graph node kinds for compiled mono DSP graphs.
pub(all) enum DspNodeKind {
  Constant
  Oscillator
  Noise
  Adsr
  Biquad
  Delay
  Gain
  Mul
  Mix
  Clip
  Output
  Pan
  StereoGain
  StereoClip
  StereoBiquad
  StereoDelay
  StereoMixDown
  StereoOutput
} derive(Eq, Debug)

///|
/// Runtime-updatable numeric graph parameter slots.
pub(all) enum GraphParamSlot {
  Value0
  Value1
  Value2
  Value3
  DelaySamples
} derive(Eq, Debug)

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

///|
/// Unified runtime control kinds for compiled graphs.
pub(all) enum GraphControlKind {
  GateOn
  GateOff
  SetParam
} derive(Eq)

///|
const NYQUIST_RATIO : Double = 0.5

///|
const MONO_SIGNAL_SHAPE : Int = 0

///|
const STEREO_SIGNAL_SHAPE : Int = 1

///|
/// Runtime control message for a compiled graph.
pub struct GraphControl {
  priv kind : GraphControlKind
  priv node_index : Int
  priv slot : GraphParamSlot
  priv value : Double
}

///|
#alias(new)
fn GraphControl::GraphControl(
  kind : GraphControlKind,
  node_index : Int,
  slot : GraphParamSlot,
  value : Double,
) -> GraphControl {
  { kind, node_index, slot, value }
}

///|
pub fn GraphControl::kind(self : GraphControl) -> GraphControlKind {
  self.kind
}

///|
pub fn GraphControl::node_index(self : GraphControl) -> Int {
  self.node_index
}

///|
pub fn GraphControl::slot(self : GraphControl) -> GraphParamSlot {
  self.slot
}

///|
pub fn GraphControl::value(self : GraphControl) -> Double {
  self.value
}

///|
/// Flat graph node representation for compiled DSP graphs.
///
/// Design decision: DspNode uses a flat struct with generic fields (value0-value3)
/// rather than per-kind structs or a tagged union with payloads. This is deliberate:
///
/// 1. **Flat memory layout** — all nodes are the same size, enabling fixed-array
///    storage without boxing. Critical for zero-allocation audio processing.
/// 2. **Copy-on-update** — node_with_value0/value1/delay_samples create updated
///    copies without knowing the node kind, supporting runtime parameter changes.
/// 3. **Serialization** — uniform layout simplifies graph serialization for
///    hot-swap and topology editing.
///
/// The cost is that field semantics depend on `kind` — see each constructor
/// (e.g. `DspNode::oscillator`, `DspNode::biquad`) for the field mapping.
pub struct DspNode {
  priv kind : DspNodeKind
  priv input0 : Int
  priv input1 : Int
  priv value0 : Double
  priv value1 : Double
  priv value2 : Double
  priv value3 : Double
  priv waveform : Waveform
  priv filter_mode : BiquadMode
  priv delay_max_samples : Int
  priv delay_samples : Int
  priv seed : UInt
}

///|
#alias(new)
fn DspNode::DspNode(
  kind : DspNodeKind,
  input0 : Int,
  input1 : Int,
  value0 : Double,
  value1 : Double,
  value2 : Double,
  value3 : Double,
  waveform : Waveform,
  filter_mode : BiquadMode,
  delay_max_samples : Int,
  delay_samples : Int,
  seed : UInt,
) -> DspNode {
  {
    kind,
    input0,
    input1,
    value0,
    value1,
    value2,
    value3,
    waveform,
    filter_mode,
    delay_max_samples,
    delay_samples,
    seed,
  }
}

///|
pub fn DspNode::kind(self : DspNode) -> DspNodeKind {
  self.kind
}

///|
pub fn DspNode::input0(self : DspNode) -> Int {
  self.input0
}

///|
pub fn DspNode::input1(self : DspNode) -> Int {
  self.input1
}

///|
pub fn DspNode::value0(self : DspNode) -> Double {
  self.value0
}

///|
pub fn DspNode::value1(self : DspNode) -> Double {
  self.value1
}

///|
pub fn DspNode::value2(self : DspNode) -> Double {
  self.value2
}

///|
pub fn DspNode::value3(self : DspNode) -> Double {
  self.value3
}

///|
pub fn DspNode::waveform(self : DspNode) -> Waveform {
  self.waveform
}

///|
pub fn DspNode::filter_mode(self : DspNode) -> BiquadMode {
  self.filter_mode
}

///|
pub fn DspNode::delay_max_samples(self : DspNode) -> Int {
  self.delay_max_samples
}

///|
pub fn DspNode::delay_samples(self : DspNode) -> Int {
  self.delay_samples
}

///|
pub fn DspNode::seed(self : DspNode) -> UInt {
  self.seed
}

///|
/// Create a constant-value node.
pub fn DspNode::constant(value : Double) -> DspNode {
  DspNode::new(
    DspNodeKind::Constant,
    -1,
    -1,
    value,
    0.0,
    0.0,
    0.0,
    Waveform::Sine,
    BiquadMode::LowPass,
    0,
    0,
    0U,
  )
}

///|
/// Create an oscillator node with a fixed frequency.
pub fn DspNode::oscillator(waveform : Waveform, freq : Double) -> DspNode {
  DspNode::new(
    DspNodeKind::Oscillator,
    -1,
    -1,
    freq,
    0.0,
    0.0,
    0.0,
    waveform,
    BiquadMode::LowPass,
    0,
    0,
    0U,
  )
}

///|
/// Create an oscillator node that reads frequency from another node (FM mode).
pub fn DspNode::oscillator_from(input : Int, waveform : Waveform) -> DspNode {
  DspNode::new(
    DspNodeKind::Oscillator,
    input,
    -1,
    0.0,
    0.0,
    0.0,
    0.0,
    waveform,
    BiquadMode::LowPass,
    0,
    0,
    0U,
  )
}

///|
/// Create a white-noise source node.
pub fn DspNode::noise(seed : UInt) -> DspNode {
  DspNode::new(
    DspNodeKind::Noise,
    -1,
    -1,
    0.0,
    0.0,
    0.0,
    0.0,
    Waveform::Sine,
    BiquadMode::LowPass,
    0,
    0,
    seed,
  )
}

///|
/// Create an ADSR envelope source node.
///
/// Use `CompiledDsp::apply_control(GraphControl::gate_on(...))` and
/// `CompiledDsp::apply_control(GraphControl::gate_off(...))` to drive the
/// envelope after compilation.
pub fn DspNode::adsr(
  attack_ms~ : Double,
  decay_ms~ : Double,
  sustain~ : Double,
  release_ms~ : Double,
) -> DspNode {
  DspNode::new(
    DspNodeKind::Adsr,
    -1,
    -1,
    attack_ms,
    decay_ms,
    sustain,
    release_ms,
    Waveform::Sine,
    BiquadMode::LowPass,
    0,
    0,
    0U,
  )
}

///|
/// Create a fixed-parameter biquad node from one upstream node.
pub fn DspNode::biquad(
  input~ : Int,
  mode~ : BiquadMode,
  cutoff_hz~ : Double,
  q~ : Double,
) -> DspNode {
  DspNode::new(
    DspNodeKind::Biquad,
    input,
    -1,
    cutoff_hz,
    q,
    0.0,
    0.0,
    Waveform::Sine,
    mode,
    0,
    0,
    0U,
  )
}

///|
/// Create a fixed-delay node from one upstream node.
pub fn DspNode::delay(
  input~ : Int,
  max_delay_samples~ : Int,
  delay_samples? : Int = graph_default_delay_samples(max_delay_samples),
  feedback? : Double = 0.0,
) -> DspNode {
  DspNode::new(
    DspNodeKind::Delay,
    input,
    -1,
    feedback,
    0.0,
    0.0,
    0.0,
    Waveform::Sine,
    BiquadMode::LowPass,
    max_delay_samples,
    delay_samples,
    0U,
  )
}

///|
/// Create an in-place gain node from one upstream node.
pub fn DspNode::gain(input : Int, amount : Double) -> DspNode {
  DspNode::new(
    DspNodeKind::Gain,
    input,
    -1,
    amount,
    0.0,
    0.0,
    0.0,
    Waveform::Sine,
    BiquadMode::LowPass,
    0,
    0,
    0U,
  )
}

///|
/// Create a gain node with envelope modulation from two upstream nodes.
/// The output is: input_signal * envelope_buffer * amount.
pub fn DspNode::envelope_gain(
  input~ : Int,
  envelope~ : Int,
  amount~ : Double,
) -> DspNode {
  DspNode::new(
    DspNodeKind::Gain,
    input,
    envelope,
    amount,
    0.0,
    0.0,
    0.0,
    Waveform::Sine,
    BiquadMode::LowPass,
    0,
    0,
    0U,
  )
}

///|
/// Create a sample-wise multiply node from two upstream nodes.
pub fn DspNode::mul(left : Int, right : Int) -> DspNode {
  DspNode::new(
    DspNodeKind::Mul,
    left,
    right,
    0.0,
    0.0,
    0.0,
    0.0,
    Waveform::Sine,
    BiquadMode::LowPass,
    0,
    0,
    0U,
  )
}

///|
/// Create a mix node from two upstream nodes.
pub fn DspNode::mix(left : Int, right : Int) -> DspNode {
  DspNode::new(
    DspNodeKind::Mix,
    left,
    right,
    0.0,
    0.0,
    0.0,
    0.0,
    Waveform::Sine,
    BiquadMode::LowPass,
    0,
    0,
    0U,
  )
}

///|
/// Create a clip node from one upstream node.
pub fn DspNode::clip(input : Int, threshold : Double) -> DspNode {
  DspNode::new(
    DspNodeKind::Clip,
    input,
    -1,
    threshold,
    0.0,
    0.0,
    0.0,
    Waveform::Sine,
    BiquadMode::LowPass,
    0,
    0,
    0U,
  )
}

///|
/// Mark the final output node.
pub fn DspNode::output(input : Int) -> DspNode {
  DspNode::new(
    DspNodeKind::Output,
    input,
    -1,
    0.0,
    0.0,
    0.0,
    0.0,
    Waveform::Sine,
    BiquadMode::LowPass,
    0,
    0,
    0U,
  )
}

///|
/// Create a terminal stereo pan node from one mono upstream node.
pub fn DspNode::pan(input : Int, position : Double) -> DspNode {
  DspNode::new(
    DspNodeKind::Pan,
    input,
    -1,
    position,
    0.0,
    0.0,
    0.0,
    Waveform::Sine,
    BiquadMode::LowPass,
    0,
    0,
    0U,
  )
}

///|
/// Create a stereo gain node from one stereo upstream node.
pub fn DspNode::stereo_gain(input~ : Int, amount~ : Double) -> DspNode {
  DspNode::new(
    DspNodeKind::StereoGain,
    input,
    -1,
    amount,
    0.0,
    0.0,
    0.0,
    Waveform::Sine,
    BiquadMode::LowPass,
    0,
    0,
    0U,
  )
}

///|
/// Create a stereo clip node from one stereo upstream node.
pub fn DspNode::stereo_clip(input~ : Int, threshold~ : Double) -> DspNode {
  DspNode::new(
    DspNodeKind::StereoClip,
    input,
    -1,
    threshold,
    0.0,
    0.0,
    0.0,
    Waveform::Sine,
    BiquadMode::LowPass,
    0,
    0,
    0U,
  )
}

///|
/// Create a stereo biquad node from one stereo upstream node.
pub fn DspNode::stereo_biquad(
  input~ : Int,
  mode~ : BiquadMode,
  cutoff_hz~ : Double,
  q~ : Double,
) -> DspNode {
  DspNode::new(
    DspNodeKind::StereoBiquad,
    input,
    -1,
    cutoff_hz,
    q,
    0.0,
    0.0,
    Waveform::Sine,
    mode,
    0,
    0,
    0U,
  )
}

///|
/// Create a stereo delay node from one stereo upstream node.
pub fn DspNode::stereo_delay(
  input~ : Int,
  max_delay_samples~ : Int,
  delay_samples? : Int = graph_default_delay_samples(max_delay_samples),
  feedback? : Double = 0.0,
) -> DspNode {
  DspNode::new(
    DspNodeKind::StereoDelay,
    input,
    -1,
    feedback,
    0.0,
    0.0,
    0.0,
    Waveform::Sine,
    BiquadMode::LowPass,
    max_delay_samples,
    delay_samples,
    0U,
  )
}

///|
/// Create a fixed-policy stereo-to-mono fold-down node.
pub fn DspNode::stereo_mixdown(input : Int) -> DspNode {
  DspNode::new(
    DspNodeKind::StereoMixDown,
    input,
    -1,
    0.0,
    0.0,
    0.0,
    0.0,
    Waveform::Sine,
    BiquadMode::LowPass,
    0,
    0,
    0U,
  )
}

///|
/// Mark the final stereo output node.
pub fn DspNode::stereo_output(input : Int) -> DspNode {
  DspNode::new(
    DspNodeKind::StereoOutput,
    input,
    -1,
    0.0,
    0.0,
    0.0,
    0.0,
    Waveform::Sine,
    BiquadMode::LowPass,
    0,
    0,
    0U,
  )
}

///|
/// Create a runtime gate-on control targeting an original authoring index.
pub fn GraphControl::gate_on(node_index : Int) -> GraphControl {
  GraphControl::new(
    GraphControlKind::GateOn,
    node_index,
    GraphParamSlot::Value0,
    0.0,
  )
}

///|
/// Create a runtime gate-off control targeting an original authoring index.
pub fn GraphControl::gate_off(node_index : Int) -> GraphControl {
  GraphControl::new(
    GraphControlKind::GateOff,
    node_index,
    GraphParamSlot::Value0,
    0.0,
  )
}

///|
/// Create a runtime numeric parameter update targeting an original authoring
/// index.
pub fn GraphControl::set_param(
  node_index : Int,
  slot : GraphParamSlot,
  value : Double,
) -> GraphControl {
  GraphControl::new(GraphControlKind::SetParam, node_index, slot, value)
}

///|
fn remap_node_inputs(node : DspNode, remap : FixedArray[Int]) -> DspNode {
  DspNode::new(
    node.kind,
    remapped_input(node.input0, remap),
    remapped_input(node.input1, remap),
    node.value0,
    node.value1,
    node.value2,
    node.value3,
    node.waveform,
    node.filter_mode,
    node.delay_max_samples,
    node.delay_samples,
    node.seed,
  )
}

///|
fn node_with_value0(node : DspNode, value0 : Double) -> DspNode {
  DspNode::new(
    node.kind,
    node.input0,
    node.input1,
    value0,
    node.value1,
    node.value2,
    node.value3,
    node.waveform,
    node.filter_mode,
    node.delay_max_samples,
    node.delay_samples,
    node.seed,
  )
}

///|
fn node_with_value1(node : DspNode, value1 : Double) -> DspNode {
  DspNode::new(
    node.kind,
    node.input0,
    node.input1,
    node.value0,
    value1,
    node.value2,
    node.value3,
    node.waveform,
    node.filter_mode,
    node.delay_max_samples,
    node.delay_samples,
    node.seed,
  )
}

///|
fn node_with_delay_samples(node : DspNode, delay_samples : Int) -> DspNode {
  DspNode::new(
    node.kind,
    node.input0,
    node.input1,
    node.value0,
    node.value1,
    node.value2,
    node.value3,
    node.waveform,
    node.filter_mode,
    node.delay_max_samples,
    delay_samples,
    node.seed,
  )
}

///|
fn remapped_input(input : Int, remap : FixedArray[Int]) -> Int {
  if input >= 0 {
    remap[input]
  } else {
    -1
  }
}