///|
/// Deterministic, in-memory filesystem for tests, tools, and non-native
/// hosts. All stored paths are normalized at construction time.
///
/// Fields stay private so callers cannot bypass path normalization or forge
/// adapter counters. Mutation remains available through `put`.
pub struct MemoryFileSystem {
  priv files : Map[String, String]
  priv home : String?
  priv mut reads : Int
  priv mut globs : Int
}

///|
/// Create an in-memory filesystem from display paths and text contents.
pub fn MemoryFileSystem::new(
  files : Map[String, String],
  home? : String,
) -> MemoryFileSystem {
  let normalized : Map[String, String] = Map([])
  for path, text in files {
    normalized[normalize_path(path)] = text
  }
  { files: normalized, home, reads: 0, globs: 0 }
}

///|
/// Update or add a virtual file using its normalized path.
pub fn MemoryFileSystem::put(
  self : MemoryFileSystem,
  path : String,
  text : String,
) -> Unit {
  self.files[normalize_path(path)] = text
}

///|
/// Number of `read_text` calls made through the `FileSystem` interface.
pub fn MemoryFileSystem::read_count(self : MemoryFileSystem) -> Int {
  self.reads
}

///|
/// Number of `glob` calls made through the `FileSystem` interface.
pub fn MemoryFileSystem::glob_count(self : MemoryFileSystem) -> Int {
  self.globs
}

///|
/// View this deterministic implementation through the portable backend trait.
pub fn MemoryFileSystem::as_file_system(self : MemoryFileSystem) -> &FileSystem {
  self as &FileSystem
}

///|
fn chars(text : String) -> Array[Char] {
  let result : Array[Char] = []
  for char in text {
    result.push(char)
  }
  result
}

///|
/// Match one normalized virtual path in O(pattern × path) time and O(path)
/// memory. `*` and `?` do not consume `/`, which keeps `conf.d/*.conf`
/// confined to its directory level.
fn path_glob_matches(pattern : String, path : String) -> Bool {
  let pattern_chars = chars(pattern)
  let path_chars = chars(path)
  let mut previous = Array::make(path_chars.length() + 1, false)
  previous[0] = true
  for token in pattern_chars {
    let current = Array::make(path_chars.length() + 1, false)
    if token == '*' {
      current[0] = previous[0]
      for index in 1..<=path_chars.length() {
        current[index] = previous[index] ||
          (current[index - 1] && path_chars[index - 1] != '/')
      }
    } else {
      for index in 1..<=path_chars.length() {
        current[index] = previous[index - 1] &&
          (if token == '?' {
            path_chars[index - 1] != '/'
          } else {
            token == path_chars[index - 1]
          })
      }
    }
    previous = current
  }
  previous[path_chars.length()]
}

///|
impl FileSystem for MemoryFileSystem with fn read_text(
  self : MemoryFileSystem,
  path : String,
  max_bytes? : Int,
) -> Result[String, FileSystemError] {
  self.reads += 1
  let normalized = normalize_path(path)
  match self.files.get(normalized) {
    Some(text) =>
      match max_bytes {
        Some(limit) if @utf8.encode(text).length() > limit =>
          Err(ReadByteLimitExceeded(path~, limit~))
        _ => Ok(text)
      }
    None => Err(NotFound(path~))
  }
}

///|
impl FileSystem for MemoryFileSystem with fn canonical_identity(
  self : MemoryFileSystem,
  path : String,
) -> Result[String, FileSystemError] {
  let normalized = normalize_path(path)
  if self.files.contains(normalized) {
    Ok(normalized)
  } else {
    Err(NotFound(path~))
  }
}

///|
impl FileSystem for MemoryFileSystem with fn glob(
  self : MemoryFileSystem,
  pattern : String,
  max_matches? : Int,
) -> Result[Array[String], FileSystemError] {
  self.globs += 1
  let normalized = normalize_path(pattern)
  let matches : Array[String] = []
  for path in self.files.keys() {
    if path_glob_matches(normalized, path) {
      match max_matches {
        Some(limit) if matches.length() >= limit =>
          return Err(GlobMatchLimitExceeded(path=pattern, limit~))
        _ => ()
      }
      matches.push(path)
    }
  }
  matches.sort()
  Ok(matches)
}

///|
impl FileSystem for MemoryFileSystem with fn home_dir(self : MemoryFileSystem) -> String? {
  self.home
}