///|
/// Phrase query whose final token is expanded from the segment term
/// dictionary. This is the primitive behind search-as-you-type query strings.
pub struct PhrasePrefixQuery {
  field_id : FieldId
  exact_texts : ReadOnlyArray[String]
  final_prefix : String
  slop : Int
  max_expansions : Int
}

///|
pub fn PhrasePrefixQuery::new(
  field_id : FieldId,
  exact_texts : Array[String],
  final_prefix : String,
) -> PhrasePrefixQuery {
  {
    field_id,
    exact_texts: ReadOnlyArray::from_array(exact_texts.copy()),
    final_prefix,
    slop: 0,
    max_expansions: 64,
  }
}

///|
pub fn PhrasePrefixQuery::with_options(
  self : PhrasePrefixQuery,
  slop : Int,
  max_expansions : Int,
) -> PhrasePrefixQuery {
  guard slop >= 0 else { abort("phrase-prefix slop must be non-negative") }
  guard max_expansions > 0 else {
    abort("phrase-prefix expansion limit must be positive")
  }
  {
    field_id: self.field_id,
    exact_texts: self.exact_texts,
    final_prefix: self.final_prefix,
    slop,
    max_expansions,
  }
}

///|
priv struct PhrasePrefixWeight {
  query : PhrasePrefixQuery
  statistics : SearchStatistics
}

///|
impl Weight for PhrasePrefixWeight with fn scorer(self, segment) {
  let clauses : Array[BooleanClause] = []
  let expanded = segment
    .term_dictionary()
    .terms_with_prefix(self.query.field_id, self.query.final_prefix)
  let mut count = 0
  for term in expanded {
    if count >= self.query.max_expansions {
      break
    }
    let texts : Array[String] = []
    for text in self.query.exact_texts {
      texts.push(text)
    }
    texts.push(term.text)
    clauses.push(
      BooleanClause::new(
        Should,
        PhraseQuery::new(self.query.field_id, texts).with_slop(self.query.slop),
      ),
    )
    count += 1
  }
  Query::weight(BooleanQuery::new(clauses), self.statistics).scorer(segment)
}

///|
pub impl Query for PhrasePrefixQuery with fn weight(self, statistics) {
  PhrasePrefixWeight::{ query: self, statistics } as &Weight
}