///|
/// InternId is a lightweight identifier for interned values.
/// Same value always gets the same ID, enabling O(1) comparison.
pub(all) struct InternId(Int) derive(Eq, Compare, Hash, Debug)

///|
/// Show prints "InternId()", preserving the previous derived-Show output.
pub impl Show for InternId with fn output(self, logger) {
  logger.write_string("InternId(")
  logger.write_string(self.0.to_string())
  logger.write_string(")")
}

///|
/// Get the raw integer value of the InternId.
pub fn InternId::get(self : InternId) -> Int {
  self.0
}

///|
/// Create an InternId from a raw integer.
/// Warning: Only use this if you know the ID is valid.
pub fn InternId::from_raw(value : Int) -> InternId {
  InternId(value)
}

///|
/// Intern stores deduplicated values and assigns stable IDs.
/// - Same value always returns same ID
/// - ID can be used to retrieve original value
/// - Useful for strings, symbols, AST nodes
pub struct Intern[V] {
  /// Unique index for this ingredient
  ingredient_index : Int
  /// Durability (typically High since interned values rarely change)
  durability : Durability
  /// Value -> ID mapping
  value_to_id : @hashmap.HashMap[V, InternId]
  /// ID -> Value mapping (for reverse lookup)
  id_to_value : Array[V]
  /// Revision when this intern was last modified
  mut changed_at : Revision
}

///|
/// Create a new Intern with the given ingredient index.
/// Default durability is High (interned values rarely change).
pub fn[V] Intern::new(ingredient_index : Int) -> Intern[V] {
  {
    ingredient_index,
    durability: Durability::High,
    value_to_id: @hashmap.HashMap::default(),
    id_to_value: [],
    changed_at: Revision::zero(),
  }
}

///|
/// Create a new Intern with custom durability.
pub fn[V] Intern::new_with_durability(
  ingredient_index : Int,
  durability : Durability,
) -> Intern[V] {
  {
    ingredient_index,
    durability,
    value_to_id: @hashmap.HashMap::default(),
    id_to_value: [],
    changed_at: Revision::zero(),
  }
}

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

///|
/// Get the durability of this intern.
pub fn[V] Intern::get_durability(self : Intern[V]) -> Durability {
  self.durability
}

///|
/// Intern a value and return its ID.
/// If the value was already interned, returns the existing ID.
/// Records a dependency if there's an active query.
pub fn[V : Hash + Eq] Intern::intern(
  self : Intern[V],
  rt : Runtime,
  value : V,
) -> InternId {
  match self.value_to_id.get(value) {
    Some(id) => {
      // Value already interned, record dependency and return existing ID
      if rt.has_active_query() {
        rt.record_dependency(
          self.ingredient_index,
          id.0,
          self.changed_at,
          self.durability,
        )
        |> ignore
      }
      id
    }
    None => {
      // New value, create ID and store
      let id = InternId(self.id_to_value.length())
      self.value_to_id.set(value, id)
      self.id_to_value.push(value)
      // Update changed_at (new value added)
      self.changed_at = rt.current_revision()
      // Record dependency
      if rt.has_active_query() {
        rt.record_dependency(
          self.ingredient_index,
          id.0,
          self.changed_at,
          self.durability,
        )
        |> ignore
      }
      id
    }
  }
}

///|
/// Look up a value by its ID.
/// Returns None if the ID is invalid.
/// Records a dependency if there's an active query.
pub fn[V] Intern::lookup(self : Intern[V], rt : Runtime, id : InternId) -> V? {
  if id.0 >= 0 && id.0 < self.id_to_value.length() {
    // Record dependency
    if rt.has_active_query() {
      rt.record_dependency(
        self.ingredient_index,
        id.0,
        self.changed_at,
        self.durability,
      )
      |> ignore
    }
    Some(self.id_to_value[id.0])
  } else {
    None
  }
}

///|
/// Check if a value has been interned.
pub fn[V : Hash + Eq] Intern::contains(self : Intern[V], value : V) -> Bool {
  self.value_to_id.contains(value)
}

///|
/// Get the ID for a value without recording a dependency.
/// Returns None if the value hasn't been interned.
pub fn[V : Hash + Eq] Intern::get_id(self : Intern[V], value : V) -> InternId? {
  self.value_to_id.get(value)
}

///|
/// Get the number of interned values.
pub fn[V] Intern::len(self : Intern[V]) -> Int {
  self.id_to_value.length()
}

///|
/// Check if the intern is empty.
pub fn[V] Intern::is_empty(self : Intern[V]) -> Bool {
  self.id_to_value.is_empty()
}

///|
/// Register this intern's verifier with the runtime.
pub fn[V] Intern::register(self : Intern[V], rt : Runtime) -> Unit {
  let intern = self
  rt.register_verifier(self.ingredient_index, fn(_key_index, revision) {
    intern.changed_at.is_after(revision)
  })
}