///|
/// Where templates come from and what they all see: the settings, the
/// filters, tests and global values, the loader and the seams for
/// translation and caching. Compiled templates are kept per environment.
pub struct Env {
  priv settings : Settings
  priv filters : Map[String, @spec.Filter]
  priv tests : Map[String, @spec.Test]
  priv globals : Map[String, @spec.Value]
  priv load : ((String) -> String?)?
  priv langs : Map[String, Lang]
  priv translate : ((String, String?, Int32?) -> String?)?
  priv cache : ((String, () -> String raise @spec.Refused) -> String raise @spec.Refused)?
  priv compiled : Map[String, Template]
}

///|
/// An environment with the built-in languages, filters, tests and globals.
///
/// `load` turns a template name into its source for `extends`, `include`,
/// `import` and `Env::template`; without it those are refused. `translate`
/// answers gettext and `{% trans %}`: given a message, its context and the
/// plural count, the translation or `None`. `cache` stores what
/// `{% once key %}` renders: given the key and a way to render, the text.
pub fn Env::new(
  settings~ : Settings = settings,
  load? : (String) -> String?,
  translate? : (String, String?, Int32?) -> String?,
  cache? : (String, () -> String raise @spec.Refused) -> String raise @spec.Refused,
) -> Env {
  let env = {
    settings,
    filters: @defaults.filters(),
    tests: @defaults.tests(),
    globals: @defaults.globals(),
    langs: Map([]),
    load,
    translate,
    cache,
    compiled: Map([]),
  }
  for lang in [@syntax.html, @syntax.markdown, @syntax.pug] {
    env.add_lang(lang)
  }
  env
}

///|
/// Register a language under its name and aliases, replacing any that
/// answer to the same; `Format.lang` and `{% block lang=… %}` find it.
pub fn Env::add_lang(self : Env, lang : Lang) -> Unit {
  self.langs[lang.name] = lang
  for other in lang.aliases {
    self.langs[other] = lang
  }
}

///|
/// Register a filter, replacing any of the same name.
pub fn Env::add_filter(
  self : Env,
  name : String,
  filter : @spec.Filter,
) -> Unit {
  self.filters[name] = filter
}

///|
/// Register a test, replacing any of the same name.
pub fn Env::add_test(self : Env, name : String, test_ : @spec.Test) -> Unit {
  self.tests[name] = test_
}

///|
/// Make a value visible to every template, replacing any of the same name;
/// a function is `Value::func(name, …)`.
pub fn Env::add_global(self : Env, name : String, value : @spec.Value) -> Unit {
  self.globals[name] = value
}

///|
/// Read a source into a template. A `name` picks the format by its
/// extension and appears in errors; `format` overrides either.
pub fn Env::compile(
  self : Env,
  source : StringView,
  name? : String,
  format? : Format,
) -> Template raise @spec.Refused {
  let chosen = match (format, name) {
    (Some(f), _) => f
    (None, Some(n)) => (self.settings.formats)(n)
    (None, None) => self.settings.format
  }
  let template = name.unwrap_or("