///|
/// Name-based registry for field indexing and query-time analysis.
pub struct TokenizerManager {
  names : Array[String]
  tokenizers : Array[&Tokenizer]
}

///|
pub fn TokenizerManager::new() -> TokenizerManager {
  { names: [], tokenizers: [] }
}

///|
pub fn TokenizerManager::register(
  self : TokenizerManager,
  name : String,
  tokenizer : &Tokenizer,
) -> Unit raise AnalysisError {
  guard @schema.valid_tokenizer_name(name) else {
    raise AnalysisError::InvalidTokenizerName(name)
  }
  match self.names.search_by(existing => existing == name) {
    Some(index) => self.tokenizers[index] = tokenizer
    None => {
      self.names.push(name)
      self.tokenizers.push(tokenizer)
    }
  }
}

///|
/// Compatibility spelling for callers that explicitly register a Tokenizer.
pub fn TokenizerManager::register_tokenizer(
  self : TokenizerManager,
  name : String,
  tokenizer : &Tokenizer,
) -> Unit raise AnalysisError {
  self.register(name, tokenizer)
}

///|
pub fn TokenizerManager::contains(
  self : TokenizerManager,
  name : String,
) -> Bool {
  self.names.search_by(existing => existing == name) is Some(_)
}

///|
pub fn TokenizerManager::snapshot(self : TokenizerManager) -> TokenizerManager {
  let names : Array[String] = []
  let tokenizers : Array[&Tokenizer] = []
  for name in self.names {
    names.push(name)
  }
  for tokenizer in self.tokenizers {
    tokenizers.push(tokenizer)
  }
  { names, tokenizers }
}

///|
/// Resolves a registered analysis pipeline as a lazy TokenStream.
pub fn TokenizerManager::token_stream(
  self : TokenizerManager,
  name : String,
  text : String,
) -> &TokenStream raise AnalysisError {
  match self.names.search_by(existing => existing == name) {
    Some(index) => self.tokenizers[index].token_stream(text)
    None => raise AnalysisError::UnknownTokenizer(name)
  }
}

///|
/// Eager convenience API for query construction, diagnostics, and tests.
pub fn TokenizerManager::analyze(
  self : TokenizerManager,
  name : String,
  text : String,
) -> Array[Token] raise AnalysisError {
  collect_token_stream(self.token_stream(name, text))
}

///|
/// Creates the built-in pipelines. NgramTokenizer remains opt-in because its
/// parameters and use case must be chosen by the application.
pub fn TokenizerManager::with_defaults() -> TokenizerManager {
  let manager = TokenizerManager::new()
  manager.register_tokenizer("raw", RawTokenizer::new()) catch {
    error => abort(error.to_string())
  }
  manager.register_tokenizer("whitespace", WhitespaceTokenizer::new()) catch {
    error => abort(error.to_string())
  }
  let default_analyzer = TextAnalyzer::new(SimpleTokenizer::new())
  default_analyzer.add_filter(RemoveLongFilter::new(40))
  default_analyzer.add_filter(LowerCaseFilter::new())
  manager.register("default", default_analyzer) catch {
    error => abort(error.to_string())
  }
  manager.register("en_stem", english_stem_analyzer()) catch {
    error => abort(error.to_string())
  }
  manager
}