///|
/// In-memory cache of parsed Gherkin features, keyed by path.
/// Mutation is controlled — only `load_*` methods can add entries.
pub struct FeatureCache {
  priv cache : Map[String, @gherkin.Feature]
}

///|
/// Create an empty feature cache.
pub fn FeatureCache::new() -> FeatureCache {
  { cache: {} }
}

///|
/// Load a feature from a file path. Idempotent — skips if already cached.
pub fn FeatureCache::load_file(
  self : FeatureCache,
  path : String,
) -> Unit raise Error {
  if self.cache.contains(path) {
    return
  }
  let content = @fs.read_file_to_string(path)
  let source = @gherkin.Source::from_string(content)
  let doc = @gherkin.parse(source)
  match doc.feature {
    Some(feature) => self.cache.set(path, feature)
    None => ()
  }
}

///|
/// Load a feature from inline text. Always overwrites existing entry.
pub fn FeatureCache::load_text(
  self : FeatureCache,
  path : String,
  content : String,
) -> Unit raise Error {
  let source = @gherkin.Source::from_string(content)
  let doc = @gherkin.parse(source)
  match doc.feature {
    Some(feature) => self.cache.set(path, feature)
    None => ()
  }
}

///|
/// Store a pre-parsed feature directly.
pub fn FeatureCache::load_parsed(
  self : FeatureCache,
  path : String,
  feature : @gherkin.Feature,
) -> Unit {
  self.cache.set(path, feature)
}

///|
/// Load a feature from any FeatureSource variant.
/// Returns an array of parse errors (empty on success).
pub fn FeatureCache::load_from_source(
  self : FeatureCache,
  source : FeatureSource,
) -> Array[ParseErrorInfo] {
  match source {
    Parsed(path, feature) => {
      self.load_parsed(path, feature)
      return []
    }
    Text(path, content) =>
      try {
        self.load_text(path, content)
        []
      } catch {
        e => [{ uri: path, message: e.to_string(), line: None }]
      }
    File(path) =>
      try {
        self.load_file(path)
        []
      } catch {
        e => [{ uri: path, message: e.to_string(), line: None }]
      }
  }
}

///|
/// Look up a cached feature by path.
pub fn FeatureCache::get(
  self : FeatureCache,
  path : String,
) -> @gherkin.Feature? {
  self.cache.get(path)
}

///|
/// Return all cached features as (path, feature) pairs.
pub fn FeatureCache::features(
  self : FeatureCache,
) -> Array[(String, @gherkin.Feature)] {
  let result : Array[(String, @gherkin.Feature)] = []
  self.cache.each(fn(k, v) { result.push((k, v)) })
  result
}

///|
/// Check if a path is cached.
pub fn FeatureCache::contains(self : FeatureCache, path : String) -> Bool {
  self.cache.contains(path)
}

///|
/// Number of cached features.
pub fn FeatureCache::length(self : FeatureCache) -> Int {
  self.cache.length()
}