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

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

///|
pub fn TokenizerManager::register(
  self : TokenizerManager,
  name : String,
  tokenizer : &Tokenizer,
) -> Unit raise AnalysisError {
  self.register_versioned(name, tokenizer, "")
}

///|
/// Registers an analyzer together with an application-defined immutable
/// resource fingerprint (dictionary hash, synonym revision, pipeline version).
pub fn TokenizerManager::register_versioned(
  self : TokenizerManager,
  name : String,
  tokenizer : &Tokenizer,
  version : String,
) -> 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
      self.versions[index] = version
    }
    None => {
      self.names.push(name)
      self.tokenizers.push(tokenizer)
      self.versions.push(version)
    }
  }
}

///|
pub fn TokenizerManager::version(
  self : TokenizerManager,
  name : String,
) -> String? {
  match self.names.search_by(existing => existing == name) {
    Some(index) => Some(self.versions[index])
    None => None
  }
}

///|
/// 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] = []
  let versions : Array[String] = []
  for name in self.names {
    names.push(name)
  }
  for tokenizer in self.tokenizers {
    tokenizers.push(tokenizer)
  }
  for version in self.versions {
    versions.push(version)
  }
  { names, tokenizers, versions }
}

///|
/// Validates every indexed text field against the registered analyzer name and
/// optional resource fingerprint stored in the Schema.
pub fn TokenizerManager::validate_schema(
  self : TokenizerManager,
  schema : Schema,
) -> Unit raise AnalysisError {
  for field_index in 0.. name
        None => abort("schema field disappeared during analyzer validation")
      }
      let expected = match schema.analyzer_version(field_id) {
        Some(version) => version
        None => ""
      }
      let actual = match self.version(name) {
        Some(version) => version
        None => raise AnalysisError::UnknownTokenizer(name)
      }
      if expected != "" && actual != expected {
        raise AnalysisError::AnalyzerVersionMismatch(name, expected, actual)
      }
    }
  }
}

///|
/// 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_versioned("raw", RawTokenizer::new(), "builtin/raw@1") catch {
    error => abort(Show::to_string(error))
  }
  manager.register_versioned(
    "whitespace",
    WhitespaceTokenizer::new(),
    "builtin/whitespace@1",
  ) catch {
    error => abort(Show::to_string(error))
  }
  let default_analyzer = TextAnalyzer::new(SimpleTokenizer::new())
  default_analyzer.add_filter(RemoveLongFilter::new(40))
  default_analyzer.add_filter(LowerCaseFilter::new())
  manager.register_versioned("default", default_analyzer, "builtin/default@1") catch {
    error => abort(Show::to_string(error))
  }
  manager.register_versioned(
    "en_stem",
    english_stem_analyzer(),
    "builtin/en_stem-porter2@1",
  ) catch {
    error => abort(Show::to_string(error))
  }
  manager
}