///|
fn normalize_path(path : String) -> String {
  let absolute = path.has_prefix("/")
  let parts : Array[String] = []
  for raw in path.split("/") {
    let part = raw.to_owned()
    match part {
      "" | "." => ()
      ".." =>
        if !parts.is_empty() && parts.last() != Some("..") {
          ignore(parts.pop())
        } else if !absolute {
          parts.push(part)
        }
      _ => parts.push(part)
    }
  }
  let normalized = parts.join("/")
  if absolute {
    "/\{normalized}"
  } else {
    normalized
  }
}

///|
fn dirname(path : String) -> String {
  match path.rev_split_once("/") {
    Some((parent, _)) => parent.to_owned()
    None => ""
  }
}

///|
fn resolve_path(base : String, id : String) -> String {
  if id.has_prefix("/") {
    normalize_path(id)
  } else if base == "" {
    normalize_path(id)
  } else {
    normalize_path("\{base}/\{id}")
  }
}

///|
/// An asynchronous in-memory stylesheet loader.
///
/// Paths are normalized on insertion and lookup. This makes it suitable for
/// deterministic tests and hosts without filesystem access.
pub struct MemoryStylesheetLoader {
  files : Map[String, String]
}

///|
pub fn MemoryStylesheetLoader::new(
  files? : Array[(String, String)] = [],
) -> MemoryStylesheetLoader {
  let normalized : Map[String, String] = Map([])
  for entry in files {
    let (path, content) = entry
    normalized[normalize_path(path)] = content
  }
  { files: normalized }
}

///|
pub fn MemoryStylesheetLoader::add(
  self : MemoryStylesheetLoader,
  path : String,
  content : String,
) -> Unit {
  self.files[normalize_path(path)] = content
}

///|
fn MemoryStylesheetLoader::resolve(
  self : MemoryStylesheetLoader,
  id : String,
  base : String,
) -> LoadedStylesheet raise CompileError {
  let path = resolve_path(base, id)
  match self.files.get(path) {
    Some(content) => { content, path, base: dirname(path) }
    None => raise StylesheetNotFound(path)
  }
}

///|
pub impl StylesheetLoader for MemoryStylesheetLoader with fn load(
  self,
  id,
  base,
) {
  self.resolve(id, base)
}

///|
pub impl SyncStylesheetLoader for MemoryStylesheetLoader with fn load(
  self,
  id,
  base,
) {
  self.resolve(id, base)
}