///|
/// InputValue stores a value along with its revision metadata.
struct InputValue[V] {
  value : V
  changed_at : Revision
}

///|
/// Input represents an external input to the incremental computation system.
/// Values can be set at any time, which increments the revision.
pub struct Input[K, V] {
  /// Unique index for this input
  ingredient_index : Int
  /// Durability of this input (how often it changes)
  durability : Durability
  /// Stored values
  values : @hashmap.HashMap[K, InputValue[V]]
  /// Hash to changed_at mapping for verifier lookup
  hash_to_changed_at : @hashmap.HashMap[Int, Revision]
}

///|
/// Create a new Input with the given ingredient index and default Low durability.
pub fn[K, V] Input::new(ingredient_index : Int) -> Input[K, V] {
  {
    ingredient_index,
    durability: Durability::Low,
    values: @hashmap.HashMap::default(),
    hash_to_changed_at: @hashmap.HashMap::default(),
  }
}

///|
/// Create a new Input with the given ingredient index and durability.
pub fn[K, V] Input::new_with_durability(
  ingredient_index : Int,
  durability : Durability,
) -> Input[K, V] {
  {
    ingredient_index,
    durability,
    values: @hashmap.HashMap::default(),
    hash_to_changed_at: @hashmap.HashMap::default(),
  }
}

///|
/// Register this input's verifier with the runtime.
/// This enables deep verify for queries that depend on this input.
pub fn[K, V] Input::register(self : Input[K, V], rt : Runtime) -> Unit {
  // Create a verifier closure that captures the hash-to-revision mapping
  // key_index is the hash of the key (as recorded in record_dependency)
  let hash_to_changed_at = self.hash_to_changed_at
  rt.register_verifier(self.ingredient_index, fn(key_index, revision) {
    // Look up the specific key's changed_at by hash
    match hash_to_changed_at.get(key_index) {
      Some(changed_at) => changed_at.is_after(revision)
      None => true // Key not found, consider it changed (conservative)
    }
  })
}

///|
/// Get the ingredient index.
pub fn[K, V] Input::get_index(self : Input[K, V]) -> Int {
  self.ingredient_index
}

///|
/// Get the durability of this input.
pub fn[K, V] Input::get_durability(self : Input[K, V]) -> Durability {
  self.durability
}

///|
/// Set a value in the input. Returns the new revision.
/// If the value hasn't changed, no revision bump occurs.
pub fn[K : Hash + Eq, V : Eq] Input::set(
  self : Input[K, V],
  rt : Runtime,
  key : K,
  value : V,
) -> Revision {
  // Check if value actually changed
  match self.values.get(key) {
    Some(existing) =>
      if existing.value == value {
        // No change, return current revision
        return rt.current_revision()
      }
    None => ()
  }
  // Value changed, increment revision with durability
  let new_revision = rt.increment_revision(self.durability)
  self.values.set(key, { value, changed_at: new_revision })
  // Update hash-to-changed_at mapping for verifier lookup
  self.hash_to_changed_at.set(key.hash(), new_revision)
  new_revision
}

///|
/// Get a value from the input. Records a dependency if there's an active query.
pub fn[K : Hash + Eq, V] Input::get(
  self : Input[K, V],
  rt : Runtime,
  key : K,
) -> V? {
  match self.values.get(key) {
    Some(entry) => {
      // Record dependency for the active query with durability
      let key_hash = key.hash()
      rt.record_dependency(
        self.ingredient_index,
        key_hash,
        entry.changed_at,
        self.durability,
      )
      |> ignore
      Some(entry.value)
    }
    None => None
  }
}

///|
/// Check if a key exists in the input.
pub fn[K : Hash + Eq, V] Input::contains(self : Input[K, V], key : K) -> Bool {
  self.values.contains(key)
}

///|
/// Get the changed_at revision for a key.
pub fn[K : Hash + Eq, V] Input::changed_at(
  self : Input[K, V],
  key : K,
) -> Revision? {
  match self.values.get(key) {
    Some(entry) => Some(entry.changed_at)
    None => None
  }
}

///|
/// Check if the input might have changed after a given revision.
pub fn[K : Hash + Eq, V] Input::maybe_changed_after(
  self : Input[K, V],
  key : K,
  revision : Revision,
) -> Bool {
  match self.values.get(key) {
    Some(entry) => entry.changed_at.is_after(revision)
    None => true // Missing key is considered "changed"
  }
}