///|
/// A caller-owned, multi-entry construction cache for keyed view builders.
///
/// Each entry is stored under a stable `builder_key` and records the
/// `content_key` used to build it. A lookup is a hit only when both match; a
/// miss replaces that builder's entry.
///
/// This is deliberately not dependency tracking or component state. The caller
/// must include every model value, environment/theme value, conditional branch,
/// event-handler payload, map-transform capture, and other construction input
/// in `content_key`. An incomplete key serves a stale view.
///
/// The cache does not evict automatically. Use `remove` when a keyed item or
/// conditional branch disappears, and `clear` when its owning screen/root is
/// torn down. Element identity, reconciliation, layout, paint, and transient
/// control state remain owned by the runtime.
///
/// `ViewBuilderCache` is a handle: copying the struct aliases the same entries
/// and counters. Keep one owner per bounded collection/screen. Use `ViewMemo`
/// instead for one independently invalidated subtree.
pub struct ViewBuilderCache[Msg] {
  priv state : Ref[ViewBuilderCacheState[Msg]]
}

///|
/// A caller-readable snapshot of one builder cache's construction counters.
///
/// A miss is counted when a lookup is not served from the cache. A build is
/// counted after `build` returns successfully. They are normally equal, but
/// remain separate so failed work cannot be mistaken for a completed build.
///
/// Counters cover the cache handle's lifetime. `clear` removes entries without
/// resetting these counters.
#derive(Eq, Debug)
pub struct ViewBuilderCacheStats {
  priv build_count : Int32
  priv hit_count : Int32
  priv miss_count : Int32
  priv entry_count : Int32
}

///|
pub extend ViewBuilderCacheStats with Debug::{to_repr}

///|
pub extend ViewBuilderCacheStats with Eq::{not_equal, equal}

///|
priv struct ViewBuilderCacheState[Msg] {
  entries : Map[DeclarationKey, ViewBuilderCacheEntry[Msg]]
  mut build_count : Int32
  mut hit_count : Int32
  mut miss_count : Int32
}

///|
priv struct ViewBuilderCacheEntry[Msg] {
  content_key : DeclarationKey
  view : View[Msg]
}

///|
pub fn[Msg] ViewBuilderCache::new() -> ViewBuilderCache[Msg] {
  {
    state: Ref({
      entries: Map([]),
      build_count: 0,
      hit_count: 0,
      miss_count: 0,
    }),
  }
}

///|
pub fn ViewBuilderCacheStats::build_count(
  self : ViewBuilderCacheStats,
) -> Int32 {
  self.build_count
}

///|
pub fn ViewBuilderCacheStats::hit_count(self : ViewBuilderCacheStats) -> Int32 {
  self.hit_count
}

///|
pub fn ViewBuilderCacheStats::miss_count(self : ViewBuilderCacheStats) -> Int32 {
  self.miss_count
}

///|
pub fn ViewBuilderCacheStats::entry_count(
  self : ViewBuilderCacheStats,
) -> Int32 {
  self.entry_count
}

///|
/// Return a snapshot of this cache's construction counters and current entry
/// count.
///
/// The counters stay inside the caller-owned cache. They are not registered
/// globally and do not appear in `RuntimeInspectorSnapshot`.
pub fn[Msg] ViewBuilderCache::stats(
  self : ViewBuilderCache[Msg],
) -> ViewBuilderCacheStats {
  {
    build_count: self.state.val.build_count,
    hit_count: self.state.val.hit_count,
    miss_count: self.state.val.miss_count,
    entry_count: self.state.val.entries.length(),
  }
}

///|
/// Return the view cached for `builder_key` when its recorded `content_key`
/// also matches. Otherwise run `build` and replace that builder's entry.
///
/// `builder_key` is the stable logical identity of the construction slot.
/// `content_key` is the complete dependency key for that slot. Use a stable
/// model-derived builder key across keyed reorders; include the reordered item's
/// content in `content_key`, not its array position in `builder_key`.
///
/// The builder should not mutate this cache's entry for the same builder key
/// while it runs. The cache is intended for synchronous, single-threaded view
/// construction.
pub fn[Msg] ViewBuilderCache::view(
  self : ViewBuilderCache[Msg],
  builder_key~ : DeclarationKey,
  content_key~ : DeclarationKey,
  build~ : () -> View[Msg],
) -> View[Msg] {
  match self.state.val.entries.get(builder_key) {
    Some(entry) if entry.content_key == content_key => {
      self.state.val.hit_count = self.state.val.hit_count + 1
      entry.view
    }
    _ => {
      self.state.val.miss_count = self.state.val.miss_count + 1
      let view = build()
      self.state.val.build_count = self.state.val.build_count + 1
      self.state.val.entries[builder_key] = { content_key, view, }
      view
    }
  }
}

///|
/// Remove one builder slot and its cached view.
///
/// Call this when a keyed item, conditional branch, or screen region leaves the
/// model. Counters remain cumulative. Removing an absent key is a no-op.
pub fn[Msg] ViewBuilderCache::remove(
  self : ViewBuilderCache[Msg],
  builder_key : DeclarationKey,
) -> Unit {
  self.state.val.entries.remove(builder_key)
}

///|
/// Remove every cached view without resetting construction counters.
///
/// Use this when the owning screen/root is torn down. Other handles that alias
/// this cache observe the cleared state.
pub fn[Msg] ViewBuilderCache::clear(self : ViewBuilderCache[Msg]) -> Unit {
  self.state.val.entries.clear()
}