///|
pub struct WebNNProgram {
  owner : WebNNGraphBuilder
  executions : Array[WebNNExecution]
  scheduler : WebNNProgramScheduler
}

///| Compiles and prepares a reusable single-slot program. On success the

///|
/// returned program owns `self`; callers must not destroy the builder.
pub async fn WebNNGraphBuilder::compile_program_single(
  self : WebNNGraphBuilder,
  input : WebNNInput,
  output_name : String,
  output : WebNNTensor,
) -> WebNNProgram {
  self.compile_program_pool_single(input, output_name, output, 1)
}

///|
pub async fn WebNNGraphBuilder::compile_program_named(
  self : WebNNGraphBuilder,
  inputs : Array[WebNNInput],
  outputs : Array[WebNNOutput],
) -> WebNNProgram {
  self.compile_program_pool_named(inputs, outputs, 1)
}

///| Compiles one graph and prepares independent input/output tensor slots.

///|
/// On success the returned program owns `self`.
pub async fn WebNNGraphBuilder::compile_program_pool_single(
  self : WebNNGraphBuilder,
  input : WebNNInput,
  output_name : String,
  output : WebNNTensor,
  pool_size : Int,
) -> WebNNProgram {
  let named_output = self.output(output_name, output) catch {
    error => {
      self.destroy()
      raise error
    }
  }
  self.compile_program_pool_named([input], [named_output], pool_size)
}

///| Compiles one named graph and prepares independent I/O tensor slots.

///|
/// On success the returned program owns `self`.
pub async fn WebNNGraphBuilder::compile_program_pool_named(
  self : WebNNGraphBuilder,
  inputs : Array[WebNNInput],
  outputs : Array[WebNNOutput],
  pool_size : Int,
) -> WebNNProgram {
  if pool_size <= 0 {
    self.destroy()
    raise @tensor.TensorError::new(
      "WebNN program execution pool size must be positive",
    )
  }
  let session = self.compile_named(inputs, outputs) catch {
    error => {
      self.destroy()
      raise error
    }
  }
  let executions : Array[WebNNExecution] = []
  for _ in 0.. {
        executions.each(fn(prepared) { prepared.destroy() })
        self.destroy()
        raise error
      }
    }
    executions.push(execution)
  }
  { owner: self, executions, scheduler: new_program_scheduler(pool_size) }
}

///|
pub async fn WebNNProgram::run(
  self : WebNNProgram,
  input_values : Array[Float],
) -> Array[Float] {
  let execution = self.executions[0]
  if execution.input_specs.length() != 1 || execution.output_specs.length() != 1 {
    raise @tensor.TensorError::new(
      "single-value run requires exactly one input and one output",
    )
  }
  let results = self.run_named([
    { name_: execution.input_specs[0].name, values_: input_values },
  ])
  results[0].values_
}

///|
pub async fn WebNNProgram::run_named(
  self : WebNNProgram,
  input_values : Array[WebNNNamedValues],
) -> Array[WebNNNamedValues] {
  enqueue_program_run(self.scheduler, fn(slot) {
    @js.from_async(async fn() { self.executions[slot].run_named(input_values) })
  }).wait()
}

///|
pub fn WebNNProgram::pool_size(self : WebNNProgram) -> Int {
  self.executions.length()
}

///|
pub fn WebNNProgram::maximum_concurrency(self : WebNNProgram) -> Int {
  program_maximum_concurrency(self.scheduler)
}

///|
pub fn WebNNProgram::destroy(self : WebNNProgram) -> Unit {
  enqueue_program_destroy(self.scheduler, fn() {
    self.executions.each(fn(execution) { execution.destroy() })
    self.owner.destroy()
  })
}