///|
pub(all) enum WebNNRuntimeFallback {
  Disabled
  Cpu
}

///|
pub struct WebNNRuntimeMetrics {
  cache_ : TfliteRunnerCacheMetrics
  cpu_cache_ : TfliteCpuRunnerCacheMetrics
  cpu_fallbacks_ : Int
}

///|
/// High-level, cache-owning entry point for executing TFLite models with WebNN.
///
/// A runtime fixes the device preference and execution-pool size for its
/// lifetime. It retains compiled programs internally, so users need only call
/// clear() when the runtime is no longer needed.
pub struct WebNNRuntime {
  preference_ : @compat.DevicePreference
  execution_pool_size_ : Int
  fallback_ : WebNNRuntimeFallback
  tflite_cache : TfliteRunnerCache
  cpu_tflite_cache : TfliteCpuRunnerCache
  mut cpu_fallbacks_ : Int
}

///|
pub fn WebNNRuntime::new(preference : @compat.DevicePreference) -> WebNNRuntime {
  {
    preference_: preference,
    execution_pool_size_: 1,
    fallback_: Disabled,
    tflite_cache: TfliteRunnerCache::new(),
    cpu_tflite_cache: TfliteCpuRunnerCache::new(),
    cpu_fallbacks_: 0,
  }
}

///|
pub fn WebNNRuntime::new_with_pool(
  preference : @compat.DevicePreference,
  execution_pool_size : Int,
) -> WebNNRuntime raise @tensor.TensorError {
  if execution_pool_size <= 0 {
    raise @tensor.TensorError::new(
      "WebNN runtime execution pool size must be positive",
    )
  }
  {
    preference_: preference,
    execution_pool_size_: execution_pool_size,
    fallback_: Disabled,
    tflite_cache: TfliteRunnerCache::new(),
    cpu_tflite_cache: TfliteCpuRunnerCache::new(),
    cpu_fallbacks_: 0,
  }
}

///|
pub fn WebNNRuntime::new_with_options(
  preference : @compat.DevicePreference,
  execution_pool_size : Int,
  cache_capacity : Int,
  fallback : WebNNRuntimeFallback,
) -> WebNNRuntime raise @tensor.TensorError {
  if execution_pool_size <= 0 {
    raise @tensor.TensorError::new(
      "WebNN runtime execution pool size must be positive",
    )
  }
  if cache_capacity <= 0 {
    raise @tensor.TensorError::new(
      "WebNN runtime cache capacity must be positive",
    )
  }
  {
    preference_: preference,
    execution_pool_size_: execution_pool_size,
    fallback_: fallback,
    tflite_cache: TfliteRunnerCache::new_with_capacity(cache_capacity),
    cpu_tflite_cache: TfliteCpuRunnerCache::new_with_capacity(cache_capacity),
    cpu_fallbacks_: 0,
  }
}

///|
/// Construct a runtime with independent WebNN-program and parsed-CPU-model
/// cache budgets. Both byte limits measure source TFLite FlatBuffer bytes.
pub fn WebNNRuntime::new_with_cache_limits(
  preference : @compat.DevicePreference,
  execution_pool_size : Int,
  cache_capacity : Int,
  cache_byte_capacity : Int,
  cpu_cache_capacity : Int,
  cpu_cache_byte_capacity : Int,
  fallback : WebNNRuntimeFallback,
) -> WebNNRuntime raise @tensor.TensorError {
  if execution_pool_size <= 0 {
    raise @tensor.TensorError::new(
      "WebNN runtime execution pool size must be positive",
    )
  }
  let tflite_cache = TfliteRunnerCache::new_with_limits(
    cache_capacity, cache_byte_capacity,
  )
  let cpu_tflite_cache = TfliteCpuRunnerCache::new_with_limits(
    cpu_cache_capacity, cpu_cache_byte_capacity,
  )
  {
    preference_: preference,
    execution_pool_size_: execution_pool_size,
    fallback_: fallback,
    tflite_cache,
    cpu_tflite_cache,
    cpu_fallbacks_: 0,
  }
}

///|
/// Construct a bounded runtime that retries failed WebNN work on the CPU.
pub fn WebNNRuntime::new_with_cpu_fallback(
  preference : @compat.DevicePreference,
  execution_pool_size : Int,
  cache_capacity : Int,
) -> WebNNRuntime raise @tensor.TensorError {
  WebNNRuntime::new_with_options(
    preference,
    execution_pool_size,
    cache_capacity,
    Cpu,
  )
}

///|
pub fn WebNNRuntime::preference(
  self : WebNNRuntime,
) -> @compat.DevicePreference {
  self.preference_
}

///|
pub fn WebNNRuntime::execution_pool_size(self : WebNNRuntime) -> Int {
  self.execution_pool_size_
}

///|
pub fn WebNNRuntime::cache_size(self : WebNNRuntime) -> Int {
  self.tflite_cache.length()
}

///|
pub fn WebNNRuntime::cache_capacity(self : WebNNRuntime) -> Int {
  self.tflite_cache.capacity()
}

///|
pub fn WebNNRuntime::cache_byte_capacity(self : WebNNRuntime) -> Int {
  self.tflite_cache.byte_capacity()
}

///|
pub fn WebNNRuntime::cache_resident_bytes(self : WebNNRuntime) -> Int {
  self.tflite_cache.resident_bytes()
}

///|
pub fn WebNNRuntime::cpu_cache_size(self : WebNNRuntime) -> Int {
  self.cpu_tflite_cache.length()
}

///|
pub fn WebNNRuntime::cpu_cache_capacity(self : WebNNRuntime) -> Int {
  self.cpu_tflite_cache.capacity()
}

///|
pub fn WebNNRuntime::cpu_cache_byte_capacity(self : WebNNRuntime) -> Int {
  self.cpu_tflite_cache.byte_capacity()
}

///|
pub fn WebNNRuntime::cpu_cache_resident_bytes(self : WebNNRuntime) -> Int {
  self.cpu_tflite_cache.resident_bytes()
}

///|
pub fn WebNNRuntime::fallback(self : WebNNRuntime) -> WebNNRuntimeFallback {
  self.fallback_
}

///|
pub fn WebNNRuntimeMetrics::hits(self : WebNNRuntimeMetrics) -> Int {
  self.cache_.hits()
}

///|
pub fn WebNNRuntimeMetrics::misses(self : WebNNRuntimeMetrics) -> Int {
  self.cache_.misses()
}

///|
pub fn WebNNRuntimeMetrics::evictions(self : WebNNRuntimeMetrics) -> Int {
  self.cache_.evictions()
}

///|
pub fn WebNNRuntimeMetrics::entries(self : WebNNRuntimeMetrics) -> Int {
  self.cache_.entries()
}

///|
pub fn WebNNRuntimeMetrics::resident_bytes(self : WebNNRuntimeMetrics) -> Int {
  self.cache_.resident_bytes()
}

///|
pub fn WebNNRuntimeMetrics::byte_capacity(self : WebNNRuntimeMetrics) -> Int {
  self.cache_.byte_capacity()
}

///|
pub fn WebNNRuntimeMetrics::cpu_cache_hits(self : WebNNRuntimeMetrics) -> Int {
  self.cpu_cache_.hits()
}

///|
pub fn WebNNRuntimeMetrics::cpu_cache_misses(self : WebNNRuntimeMetrics) -> Int {
  self.cpu_cache_.misses()
}

///|
pub fn WebNNRuntimeMetrics::cpu_cache_evictions(
  self : WebNNRuntimeMetrics,
) -> Int {
  self.cpu_cache_.evictions()
}

///|
pub fn WebNNRuntimeMetrics::cpu_cache_entries(
  self : WebNNRuntimeMetrics,
) -> Int {
  self.cpu_cache_.entries()
}

///|
pub fn WebNNRuntimeMetrics::cpu_cache_resident_bytes(
  self : WebNNRuntimeMetrics,
) -> Int {
  self.cpu_cache_.resident_bytes()
}

///|
pub fn WebNNRuntimeMetrics::cpu_cache_byte_capacity(
  self : WebNNRuntimeMetrics,
) -> Int {
  self.cpu_cache_.byte_capacity()
}

///|
pub fn WebNNRuntimeMetrics::cpu_fallbacks(self : WebNNRuntimeMetrics) -> Int {
  self.cpu_fallbacks_
}

///|
pub fn WebNNRuntime::metrics(self : WebNNRuntime) -> WebNNRuntimeMetrics {
  {
    cache_: self.tflite_cache.metrics(),
    cpu_cache_: self.cpu_tflite_cache.metrics(),
    cpu_fallbacks_: self.cpu_fallbacks_,
  }
}

///|
/// Execute arbitrary TFLite FlatBuffer bytes.
///
/// Models are keyed by their SHA-256 digest, fixed device preference, and
/// fixed execution-pool size. The runtime owns every compiled program.
pub async fn WebNNRuntime::run_tflite(
  self : WebNNRuntime,
  bytes : Bytes,
  input_values : Array[TfliteRunnerInput],
) -> Array[TfliteRunnerOutput] {
  self.tflite_cache.run_pool(
    bytes,
    self.preference_,
    self.execution_pool_size_,
    input_values,
  ) catch {
    error =>
      match self.fallback_ {
        Disabled => raise error
        Cpu => {
          self.cpu_fallbacks_ = self.cpu_fallbacks_ + 1
          self.cpu_tflite_cache.run(bytes, input_values)
        }
      }
  }
}

///|
/// Execute a model whose bytes and SHA-256 key were prepared once up front.
///
/// This preserves the TFLite I/O contract of run_tflite while avoiding a
/// repeated digest over the original FlatBuffer on cache hits.
pub async fn WebNNRuntime::run_prepared_tflite(
  self : WebNNRuntime,
  artifact : TfliteModelArtifact,
  input_values : Array[TfliteRunnerInput],
) -> Array[TfliteRunnerOutput] {
  self.tflite_cache.run_artifact_pool(
    artifact,
    self.preference_,
    self.execution_pool_size_,
    input_values,
  ) catch {
    error =>
      match self.fallback_ {
        Disabled => raise error
        Cpu => {
          self.cpu_fallbacks_ = self.cpu_fallbacks_ + 1
          self.cpu_tflite_cache.run_artifact(artifact, input_values)
        }
      }
  }
}

///|
/// Clear cached WebNN programs and parsed CPU fallback models. The return value
/// remains the number of WebNN programs removed for compatibility.
pub fn WebNNRuntime::clear(self : WebNNRuntime) -> Int {
  let cleared = self.tflite_cache.clear()
  self.cpu_tflite_cache.clear() |> ignore
  cleared
}