///|
/// Application-level ingestion status.
pub(all) enum IngestionStatus {
  Accepted
  Rejected
  Skipped
}

///|
pub fn IngestionStatus::label(self : IngestionStatus) -> String {
  match self {
    Accepted => "accepted"
    Rejected => "rejected"
    Skipped => "skipped"
  }
}

///|
/// A single ingestion diagnostic that can be shown to an operator.
pub(all) struct IngestionIssue {
  code : String
  message : String
  document_id : String
}

///|
pub fn IngestionIssue::describe(self : IngestionIssue) -> String {
  "\{self.document_id}: \{self.code}: \{self.message}"
}

///|
/// Result for one document entering an application knowledge base.
pub(all) struct IngestionResult {
  document_id : String
  status : IngestionStatus
  token_count : Int
  known_token_count : Int
  issues : Array[IngestionIssue]
}

///|
pub fn IngestionResult::accepted(self : IngestionResult) -> Bool {
  self.status is Accepted
}

///|
pub fn IngestionResult::describe(self : IngestionResult) -> String {
  "\{self.document_id}: \{self.status.label()}, tokens=\{self.token_count}, known=\{self.known_token_count}, issues=\{self.issues.length()}"
}

///|
/// Rules for deterministic, bounded document ingestion.
pub(all) struct IngestionPolicy {
  max_documents : Int
  min_tokens : Int
  require_category : Bool
  reject_unknown_only : Bool
  replace_existing : Bool
}

///|
pub fn IngestionPolicy::conservative() -> IngestionPolicy {
  {
    max_documents: 10000,
    min_tokens: 1,
    require_category: false,
    reject_unknown_only: true,
    replace_existing: true,
  }
}

///|
pub fn IngestionPolicy::demo() -> IngestionPolicy {
  {
    max_documents: 100,
    min_tokens: 1,
    require_category: false,
    reject_unknown_only: false,
    replace_existing: true,
  }
}

///|
pub fn IngestionPolicy::valid(self : IngestionPolicy) -> Bool {
  self.max_documents > 0 && self.min_tokens >= 0
}

///|
pub fn IngestionPolicy::describe(self : IngestionPolicy) -> String {
  "max_documents=\{self.max_documents}, min_tokens=\{self.min_tokens}, require_category=\{self.require_category}, reject_unknown_only=\{self.reject_unknown_only}, replace_existing=\{self.replace_existing}"
}

///|
/// A query submitted by an application user.
pub(all) struct ApplicationQuery {
  text : String
  k : Int
  filter : DocumentFilter
  threshold : Double?
}

///|
pub fn ApplicationQuery::new(
  text : String,
  k? : Int = 5,
  filter? : DocumentFilter = DocumentFilter::empty(),
  threshold? : Double,
) -> ApplicationQuery {
  { text, k, filter, threshold }
}

///|
pub fn ApplicationQuery::valid(self : ApplicationQuery) -> Bool {
  !self.text.trim().is_empty() && self.k > 0
}

///|
pub fn ApplicationQuery::describe(self : ApplicationQuery) -> String {
  let threshold = match self.threshold {
    Some(value) => value.to_string()
    None => "none"
  }
  "text=\"\{self.text}\", k=\{self.k}, threshold=\{threshold}, \{self.filter.describe()}"
}

///|
/// A stable result returned by an application knowledge base.
pub(all) struct ApplicationHit {
  document : Document
  score : Double
  rank : Int
}

///|
pub fn ApplicationHit::id(self : ApplicationHit) -> String {
  self.document.id
}

///|
pub fn ApplicationHit::text(self : ApplicationHit) -> String {
  self.document.text
}

///|
pub fn ApplicationHit::score(self : ApplicationHit) -> Double {
  self.score
}

///|
pub fn ApplicationHit::category(self : ApplicationHit) -> String {
  match self.document.metadata.get("category") {
    Some(value) => value
    None => "uncategorized"
  }
}

///|
pub fn ApplicationHit::describe(self : ApplicationHit) -> String {
  "\{self.rank}. \{self.id()} score=\{self.score.to_string()} category=\{self.category()}"
}

///|
/// A complete application query report with observability fields.
pub(all) struct ApplicationSearchReport {
  query : ApplicationQuery
  hits : Array[ApplicationHit]
  candidates : Int
  scanned : Int
  unknown_terms : Int
  filtered_documents : Int
}

///|
pub fn ApplicationSearchReport::is_empty(
  self : ApplicationSearchReport,
) -> Bool {
  self.hits.is_empty()
}

///|
pub fn ApplicationSearchReport::top(
  self : ApplicationSearchReport,
) -> ApplicationHit? {
  self.hits.get(0)
}

///|
pub fn ApplicationSearchReport::ids(
  self : ApplicationSearchReport,
) -> Array[String] {
  let result = []
  for hit in self.hits {
    result.push(hit.id())
  }
  result
}

///|
pub fn ApplicationSearchReport::describe(
  self : ApplicationSearchReport,
) -> String {
  let mut output = "query={self.query.describe()}\n"
  output = output +
    "hits={self.hits.length()}, candidates={self.candidates}, scanned={self.scanned}, unknown_terms={self.unknown_terms}, filtered_documents={self.filtered_documents}\n"
  for hit in self.hits {
    output = output + hit.describe() + "\n"
  }
  output
}

///|
/// Query history record used by CLI and embedded application shells.
pub(all) struct QueryEvent {
  sequence : Int
  query : ApplicationQuery
  result_count : Int
  top_score : Double
  candidates : Int
}

///|
pub fn QueryEvent::describe(self : QueryEvent) -> String {
  "#\{self.sequence} results=\{self.result_count}, top_score=\{self.top_score.to_string()}, candidates=\{self.candidates}, text=\"\{self.query.text}\""
}

///|
/// A bounded query history with usage counters.
pub(all) struct QuerySession {
  name : String
  max_events : Int
  events : Array[QueryEvent]
  mut next_sequence : Int
}

///|
pub fn QuerySession::new(
  name : String,
  max_events? : Int = 100,
) -> QuerySession {
  { name, max_events, events: [], next_sequence: 1 }
}

///|
pub fn QuerySession::record(
  self : QuerySession,
  query : ApplicationQuery,
  report : ApplicationSearchReport,
) -> Unit {
  let top_score = match report.top() {
    Some(hit) => hit.score()
    None => 0.0
  }
  self.events.push({
    sequence: self.next_sequence,
    query,
    result_count: report.hits.length(),
    top_score,
    candidates: report.candidates,
  })
  self.next_sequence = self.next_sequence + 1
  if self.max_events > 0 {
    while self.events.length() > self.max_events {
      let _ = self.events.remove(0)
    }
  }
}

///|
pub fn QuerySession::size(self : QuerySession) -> Int {
  self.events.length()
}

///|
pub fn QuerySession::clear(self : QuerySession) -> Unit {
  while !self.events.is_empty() {
    let _ = self.events.pop()
  }
}

///|
pub fn QuerySession::last(self : QuerySession) -> QueryEvent? {
  if self.events.is_empty() {
    None
  } else {
    self.events.get(self.events.length() - 1)
  }
}

///|
pub fn QuerySession::history(self : QuerySession) -> Array[QueryEvent] {
  let result = []
  for event in self.events {
    result.push(event)
  }
  result
}

///|
pub fn QuerySession::describe(self : QuerySession) -> String {
  let mut output = "session={self.name}, events={self.events.length()}\n"
  for event in self.events {
    output = output + event.describe() + "\n"
  }
  output
}

///|
/// Counters exposed to a health endpoint or CLI status command.
pub(all) struct UsageCounters {
  mut ingestion_attempts : Int
  mut accepted_documents : Int
  mut rejected_documents : Int
  mut skipped_documents : Int
  mut query_attempts : Int
  mut empty_queries : Int
  mut returned_hits : Int
  mut scanned_candidates : Int
}

///|
pub fn UsageCounters::new() -> UsageCounters {
  {
    ingestion_attempts: 0,
    accepted_documents: 0,
    rejected_documents: 0,
    skipped_documents: 0,
    query_attempts: 0,
    empty_queries: 0,
    returned_hits: 0,
    scanned_candidates: 0,
  }
}

///|
pub fn UsageCounters::record_ingestion(
  self : UsageCounters,
  result : IngestionResult,
) -> Unit {
  self.ingestion_attempts = self.ingestion_attempts + 1
  match result.status {
    Accepted => self.accepted_documents = self.accepted_documents + 1
    Rejected => self.rejected_documents = self.rejected_documents + 1
    Skipped => self.skipped_documents = self.skipped_documents + 1
  }
}

///|
pub fn UsageCounters::record_query(
  self : UsageCounters,
  report : ApplicationSearchReport,
) -> Unit {
  self.query_attempts = self.query_attempts + 1
  if report.hits.is_empty() {
    self.empty_queries = self.empty_queries + 1
  }
  self.returned_hits = self.returned_hits + report.hits.length()
  self.scanned_candidates = self.scanned_candidates + report.scanned
}

///|
pub fn UsageCounters::describe(self : UsageCounters) -> String {
  "ingestion=\{self.ingestion_attempts}, accepted=\{self.accepted_documents}, rejected=\{self.rejected_documents}, skipped=\{self.skipped_documents}, queries=\{self.query_attempts}, empty_queries=\{self.empty_queries}, returned_hits=\{self.returned_hits}, scanned=\{self.scanned_candidates}"
}

///|
/// A small application knowledge base built on the core embedding index.
pub(all) struct ApplicationKnowledgeBase {
  corpus : EmbeddingCorpus
  index : MoonEmbedIndex
  store : DocumentStore
  tokenizer : TextTokenizer
  policy : IngestionPolicy
  session : QuerySession
  counters : UsageCounters
}

///|
pub fn ApplicationKnowledgeBase::new(
  corpus : EmbeddingCorpus,
  signature_bits? : Int = 3,
  tokenizer? : TextTokenizer = TextTokenizer::new(),
  policy? : IngestionPolicy = IngestionPolicy::conservative(),
  session_name? : String = "default",
) -> ApplicationKnowledgeBase {
  {
    corpus,
    index: MoonEmbedIndex::from_corpus(corpus, signature_bits),
    store: DocumentStore::new(),
    tokenizer,
    policy,
    session: QuerySession::new(session_name),
    counters: UsageCounters::new(),
  }
}

///|
fn count_known_tokens(
  corpus : EmbeddingCorpus,
  tokenizer : TextTokenizer,
  text : String,
) -> (Int, Int) {
  let tokens = tokenizer.tokens(text)
  let mut known = 0
  for token in tokens {
    if corpus.has_token(token) {
      known = known + 1
    }
  }
  (tokens.length(), known)
}

///|
fn make_ingestion_issue(
  document : Document,
  code : String,
  message : String,
) -> IngestionIssue {
  { code, message, document_id: document.id }
}

///|
/// Validate a document against the current policy before adding it.
pub fn ApplicationKnowledgeBase::validate_document(
  self : ApplicationKnowledgeBase,
  document : Document,
) -> IngestionResult {
  let counts = count_known_tokens(self.corpus, self.tokenizer, document.text)
  let issues = []
  if document.id.trim().is_empty() {
    issues.push(
      make_ingestion_issue(document, "empty-id", "document id is required"),
    )
  }
  if counts.0 < self.policy.min_tokens {
    issues.push(
      make_ingestion_issue(
        document, "too-short", "document has too few retained tokens",
      ),
    )
  }
  if self.policy.require_category && document.metadata.get("category") is None {
    issues.push(
      make_ingestion_issue(
        document, "missing-category", "category metadata is required",
      ),
    )
  }
  if self.policy.reject_unknown_only && counts.0 > 0 && counts.1 == 0 {
    issues.push(
      make_ingestion_issue(
        document, "unknown-only", "no document tokens exist in the embedding corpus",
      ),
    )
  }
  let status = if issues.is_empty() { Accepted } else { Rejected }
  {
    document_id: document.id,
    status,
    token_count: counts.0,
    known_token_count: counts.1,
    issues,
  }
}

///|
/// Add or skip one document according to policy and return an audit result.
pub fn ApplicationKnowledgeBase::ingest(
  self : ApplicationKnowledgeBase,
  document : Document,
) -> IngestionResult {
  let validation = self.validate_document(document)
  if validation.status is Rejected {
    self.counters.record_ingestion(validation)
    return validation
  }
  if self.store.size() >= self.policy.max_documents {
    let skipped = {
      ..validation,
      status: Skipped,
      issues: [
        make_ingestion_issue(
          document, "capacity", "document policy capacity reached",
        ),
      ],
    }
    self.counters.record_ingestion(skipped)
    return skipped
  }
  if !self.policy.replace_existing && self.store.get(document.id) is Some(_) {
    let skipped = {
      ..validation,
      status: Skipped,
      issues: [
        make_ingestion_issue(
          document, "duplicate", "existing document is preserved",
        ),
      ],
    }
    self.counters.record_ingestion(skipped)
    return skipped
  }
  let _ = self.store.upsert(document, self.corpus)
  self.counters.record_ingestion(validation)
  validation
}

///|
/// Ingest a batch while preserving input order and returning per-document audit records.
pub fn ApplicationKnowledgeBase::ingest_many(
  self : ApplicationKnowledgeBase,
  documents : Array[Document],
) -> Array[IngestionResult] {
  let results = []
  for document in documents {
    results.push(self.ingest(document))
  }
  results
}

///|
/// Run a filtered semantic query through the application layer.
pub fn ApplicationKnowledgeBase::search(
  self : ApplicationKnowledgeBase,
  query : ApplicationQuery,
) -> ApplicationSearchReport {
  self.counters.query_attempts = self.counters.query_attempts + 1
  if !query.valid() {
    self.counters.empty_queries = self.counters.empty_queries + 1
    return {
      query,
      hits: [],
      candidates: 0,
      scanned: 0,
      unknown_terms: 0,
      filtered_documents: self.store.size(),
    }
  }
  let phrase = PhraseQuery::new(query.text, self.tokenizer)
  let embedding = self.corpus.phrase_embedding(phrase)
  match embedding {
    None => {
      self.counters.empty_queries = self.counters.empty_queries + 1
      {
        query,
        hits: [],
        candidates: 0,
        scanned: 0,
        unknown_terms: phrase.size(),
        filtered_documents: self.store.size(),
      }
    }
    Some(vector) => {
      let filtered = self.store.filter(query.filter, self.tokenizer)
      let hits = self.store.ranked_search(vector, None, None, self.store.size())
      let allowed : Map[String, Bool] = Map([])
      for document in filtered {
        allowed.set(document.id, true)
      }
      let application_hits = []
      for ranked in hits {
        if !allowed.contains(ranked.id) {
          continue
        }
        let document = match self.store.get(ranked.id) {
          Some(value) => value
          None => continue
        }
        if query.threshold is Some(threshold) && ranked.score < threshold {
          continue
        }
        if application_hits.length() < query.k {
          application_hits.push({
            document,
            score: ranked.score,
            rank: application_hits.length() + 1,
          })
        }
      }
      self.counters.returned_hits = self.counters.returned_hits +
        application_hits.length()
      self.counters.scanned_candidates = self.counters.scanned_candidates +
        hits.length()
      let report = {
        query,
        hits: application_hits,
        candidates: hits.length(),
        scanned: hits.length(),
        unknown_terms: phrase.size() - filtered.length(),
        filtered_documents: self.store.size() - filtered.length(),
      }
      self.session.record(query, report)
      report
    }
  }
}

///|
/// Retrieve a query history snapshot for an operator.
pub fn ApplicationKnowledgeBase::history(
  self : ApplicationKnowledgeBase,
) -> Array[QueryEvent] {
  self.session.history()
}

///|
/// Retrieve usage counters for diagnostics.
pub fn ApplicationKnowledgeBase::usage(
  self : ApplicationKnowledgeBase,
) -> UsageCounters {
  self.counters
}

///|
/// Return a health summary for a readiness or liveness command.
pub fn ApplicationKnowledgeBase::health(
  self : ApplicationKnowledgeBase,
) -> String {
  let corpus_report = validate_corpus(self.corpus)
  let diagnostics = self.index.diagnostics()
  "corpus=\{corpus_report.summary()}\nindex=\{diagnostics.describe()}\nstore_documents=\{self.store.size()}\npolicy=\{self.policy.describe()}\nusage=\{self.counters.describe()}"
}

///|
/// Export application documents as a deterministic line-oriented snapshot.
pub fn ApplicationKnowledgeBase::export_documents(
  self : ApplicationKnowledgeBase,
) -> String {
  let mut output = "id\tcategory\ttext\n"
  for document in self.store.docs {
    let category = match document.metadata.get("category") {
      Some(value) => value
      None => ""
    }
    output = output +
      document.id +
      "\t" +
      category +
      "\t" +
      document.text +
      "\n"
  }
  output
}

///|
/// Return the current document ids for administration and audit views.
pub fn ApplicationKnowledgeBase::document_ids(
  self : ApplicationKnowledgeBase,
) -> Array[String] {
  self.store.ids()
}

///|
/// Return the current number of accepted documents.
pub fn ApplicationKnowledgeBase::document_count(
  self : ApplicationKnowledgeBase,
) -> Int {
  self.store.size()
}

///|
/// Return sorted-by-insertion category labels used by the knowledge base.
pub fn ApplicationKnowledgeBase::categories(
  self : ApplicationKnowledgeBase,
) -> Array[String] {
  self.store.categories()
}

///|
/// Execute independent queries while keeping their input order.
pub fn ApplicationKnowledgeBase::search_many(
  self : ApplicationKnowledgeBase,
  queries : Array[ApplicationQuery],
) -> Array[ApplicationSearchReport] {
  let reports = []
  for query in queries {
    reports.push(self.search(query))
  }
  reports
}

///|
/// Clear only the interactive session while retaining the indexed documents.
pub fn ApplicationKnowledgeBase::reset_session(
  self : ApplicationKnowledgeBase,
) -> Unit {
  self.session.clear()
}

///|
/// Return a compact health flag suitable for a readiness probe.
pub fn ApplicationKnowledgeBase::ready(self : ApplicationKnowledgeBase) -> Bool {
  self.policy.valid() &&
  self.corpus.validate() &&
  self.index.corpus().validate()
}

///|
/// Aggregate the outcome of multiple independent acceptance scenarios.
pub(all) struct ScenarioMatrixReport {
  scenarios : Int
  passed : Int
  total_documents : Int
  total_queries : Int
  total_rejected : Int
  total_skipped : Int
}

///|
pub fn ScenarioMatrixReport::all_passed(self : ScenarioMatrixReport) -> Bool {
  self.scenarios > 0 && self.passed == self.scenarios
}

///|
pub fn ScenarioMatrixReport::describe(self : ScenarioMatrixReport) -> String {
  "scenarios=\{self.scenarios}, passed=\{self.passed}, documents=\{self.total_documents}, queries=\{self.total_queries}, rejected=\{self.total_rejected}, skipped=\{self.total_skipped}, all_passed=\{self.all_passed()}"
}

///|
pub fn run_scenario_matrix() -> ScenarioMatrixReport {
  let reports = run_application_scenarios()
  let mut passed = 0
  let mut documents = 0
  let mut queries = 0
  let mut rejected = 0
  let mut skipped = 0
  for report in reports {
    if report.passed() {
      passed = passed + 1
    }
    documents = documents + report.ingested
    queries = queries + report.queries
    rejected = rejected + report.rejected
    skipped = skipped + report.skipped
  }
  {
    scenarios: reports.length(),
    passed,
    total_documents: documents,
    total_queries: queries,
    total_rejected: rejected,
    total_skipped: skipped,
  }
}

///|
/// Create a compact report for a reproducible application scenario.
pub(all) struct ScenarioReport {
  name : String
  ingested : Int
  rejected : Int
  skipped : Int
  queries : Int
  nonempty_queries : Int
  expected_matches : Int
  observed_matches : Int
  expected_rejected : Int
  expected_skipped : Int
}

///|
pub fn ScenarioReport::passed(self : ScenarioReport) -> Bool {
  self.rejected == self.expected_rejected &&
  self.skipped == self.expected_skipped &&
  self.expected_matches == self.observed_matches
}

///|
pub fn ScenarioReport::describe(self : ScenarioReport) -> String {
  "\{self.name}: ingested=\{self.ingested}, rejected=\{self.rejected}, skipped=\{self.skipped}, queries=\{self.queries}, nonempty=\{self.nonempty_queries}, expected=\{self.expected_matches}, observed=\{self.observed_matches}, passed=\{self.passed()}"
}

///|
/// Build a realistic product-support knowledge base with metadata filters.
pub fn support_knowledge_base() -> ApplicationKnowledgeBase {
  let policy = IngestionPolicy::conservative()
  let tokenizer = TextTokenizer::new(
    lowercase=true,
    min_length=2,
    stop_words=Map([("the", true), ("and", true), ("is", true)]),
  )
  ApplicationKnowledgeBase::new(
    demo_corpus(),
    signature_bits=3,
    tokenizer~,
    policy~,
    session_name="support-demo",
  )
}

///|
pub fn run_support_scenario() -> ScenarioReport {
  let knowledge = support_knowledge_base()
  let documents = [
    Document::new(
      "support-royal",
      "king queen",
      metadata=Map([("category", "account")]),
    ),
    Document::new(
      "support-fruit",
      "apple orange",
      metadata=Map([("category", "billing")]),
    ),
    Document::new(
      "support-code",
      "code bug",
      metadata=Map([("category", "technical")]),
    ),
  ]
  let ingested = knowledge.ingest_many(documents)
  let mut accepted = 0
  let mut rejected = 0
  let mut skipped = 0
  for result in ingested {
    match result.status {
      Accepted => accepted = accepted + 1
      Rejected => rejected = rejected + 1
      Skipped => skipped = skipped + 1
    }
  }
  let queries = [
    ApplicationQuery::new(
      "king",
      k=1,
      filter=DocumentFilter::empty().with_category("account"),
    ),
    ApplicationQuery::new(
      "apple",
      k=1,
      filter=DocumentFilter::empty().with_category("billing"),
    ),
    ApplicationQuery::new(
      "code",
      k=1,
      filter=DocumentFilter::empty().with_category("technical"),
    ),
  ]
  let mut nonempty = 0
  let mut observed = 0
  for query in queries {
    let report = knowledge.search(query)
    if !report.is_empty() {
      nonempty = nonempty + 1
    }
    observed = observed + report.hits.length()
  }
  {
    name: "support-knowledge-base",
    ingested: accepted,
    rejected,
    skipped,
    queries: 3,
    nonempty_queries: nonempty,
    expected_matches: 3,
    observed_matches: observed,
    expected_rejected: 0,
    expected_skipped: 0,
  }
}

///|
/// Build a realistic local documentation search scenario.
pub fn run_documentation_scenario() -> ScenarioReport {
  let knowledge = ApplicationKnowledgeBase::new(
    demo_corpus(),
    tokenizer=TextTokenizer::new(min_length=2),
    policy={ ..IngestionPolicy::demo(), reject_unknown_only: true },
    session_name="docs-demo",
  )
  let documents = [
    Document::new("docs-1", "king queen", metadata=Map([("category", "guide")])),
    Document::new(
      "docs-2",
      "apple orange",
      metadata=Map([("category", "reference")]),
    ),
    Document::new(
      "docs-3",
      "code bug",
      metadata=Map([("category", "troubleshooting")]),
    ),
    Document::new(
      "docs-4",
      "unknown words",
      metadata=Map([("category", "invalid")]),
    ),
  ]
  let results = knowledge.ingest_many(documents)
  let mut accepted = 0
  let mut rejected = 0
  let mut skipped = 0
  for result in results {
    match result.status {
      Accepted => accepted = accepted + 1
      Rejected => rejected = rejected + 1
      Skipped => skipped = skipped + 1
    }
  }
  let q1 = knowledge.search(ApplicationQuery::new("king", k=2))
  let q2 = knowledge.search(ApplicationQuery::new("unknown words", k=2))
  let observed = q1.hits.length() + q2.hits.length()
  {
    name: "documentation-search",
    ingested: accepted,
    rejected,
    skipped,
    queries: 2,
    nonempty_queries: if q1.is_empty() {
      0
    } else {
      1
    },
    expected_matches: 2,
    observed_matches: observed,
    expected_rejected: 1,
    expected_skipped: 0,
  }
}

///|
/// Build a realistic regression scenario for invalid input and capacity policy.
pub fn run_ingestion_guard_scenario() -> ScenarioReport {
  let policy = {
    ..IngestionPolicy::demo(),
    max_documents: 2,
    require_category: true,
    reject_unknown_only: true,
  }
  let knowledge = ApplicationKnowledgeBase::new(
    demo_corpus(),
    policy~,
    session_name="guard-demo",
  )
  let results = knowledge.ingest_many([
    Document::new("valid-1", "king queen", metadata=Map([("category", "ok")])),
    Document::new("missing-category", "apple orange"),
    Document::new("valid-2", "code bug", metadata=Map([("category", "ok")])),
    Document::new("over-capacity", "king", metadata=Map([("category", "ok")])),
    Document::new(
      "unknown-only",
      "unseen terms",
      metadata=Map([("category", "ok")]),
    ),
  ])
  let mut accepted = 0
  let mut rejected = 0
  let mut skipped = 0
  for result in results {
    match result.status {
      Accepted => accepted = accepted + 1
      Rejected => rejected = rejected + 1
      Skipped => skipped = skipped + 1
    }
  }
  {
    name: "ingestion-guards",
    ingested: accepted,
    rejected,
    skipped,
    queries: 0,
    nonempty_queries: 0,
    expected_matches: 0,
    observed_matches: 0,
    expected_rejected: 2,
    expected_skipped: 1,
  }
}

///|
/// Run all application scenarios used by the acceptance checklist.
pub fn run_application_scenarios() -> Array[ScenarioReport] {
  [
    run_support_scenario(),
    run_documentation_scenario(),
    run_ingestion_guard_scenario(),
  ]
}

///|
pub fn all_application_scenarios_pass() -> Bool {
  for scenario in run_application_scenarios() {
    if !scenario.passed() {
      return false
    }
  }
  true
}

///|
test "support application scenario" {
  let report = run_support_scenario()
  inspect(report.passed(), content="true")
  inspect(report.ingested, content="3")
  inspect(report.nonempty_queries, content="3")
}

///|
test "documentation application scenario" {
  let report = run_documentation_scenario()
  inspect(report.rejected, content="1")
  inspect(report.ingested, content="3")
  inspect(report.observed_matches, content="2")
}

///|
test "ingestion guard scenario" {
  let report = run_ingestion_guard_scenario()
  inspect(report.ingested, content="2")
  inspect(report.rejected, content="2")
  inspect(report.skipped, content="1")
}

///|
test "application session and export" {
  let knowledge = support_knowledge_base()
  let _ = knowledge.ingest(
    Document::new("one", "king queen", metadata=Map([("category", "account")])),
  )
  let _ = knowledge.search(ApplicationQuery::new("king", k=1))
  inspect(knowledge.history().length(), content="1")
  inspect(knowledge.export_documents().contains("one\taccount"), content="true")
  inspect(knowledge.health().contains("store_documents=1"), content="true")
  inspect(knowledge.usage().accepted_documents, content="1")
}

///|
test "application boundary behavior" {
  let knowledge = support_knowledge_base()
  let empty = knowledge.search(ApplicationQuery::new("", k=3))
  inspect(empty.is_empty(), content="true")
  let invalid = knowledge.ingest(Document::new("", "king"))
  inspect(invalid.status is Rejected, content="true")
  let session = QuerySession::new("bounded", max_events=2)
  for _ in 0..<4 {
    let query = ApplicationQuery::new("king", k=1)
    let report : ApplicationSearchReport = {
      query,
      hits: [],
      candidates: 0,
      scanned: 0,
      unknown_terms: 0,
      filtered_documents: 0,
    }
    session.record(query, report)
  }
  inspect(session.size(), content="2")
}

///|
test "knowledge base administration" {
  let knowledge = support_knowledge_base()
  let results = knowledge.ingest_many([
    Document::new("a", "king", metadata=Map([("category", "account")])),
    Document::new("b", "apple", metadata=Map([("category", "billing")])),
  ])
  inspect(results.length(), content="2")
  inspect(knowledge.document_count(), content="2")
  inspect(knowledge.document_ids().length(), content="2")
  inspect(knowledge.categories().length(), content="2")
  let reports = knowledge.search_many([
    ApplicationQuery::new("king", k=1),
    ApplicationQuery::new("apple", k=1),
  ])
  inspect(reports.length(), content="2")
  inspect(knowledge.history().length(), content="2")
  inspect(knowledge.ready(), content="true")
  knowledge.reset_session()
  inspect(knowledge.history().length(), content="0")
}

///|
test "scenario matrix" {
  let matrix = run_scenario_matrix()
  inspect(matrix.scenarios, content="3")
  inspect(matrix.passed, content="3")
  inspect(matrix.all_passed(), content="true")
  inspect(matrix.total_documents > 0, content="true")
  inspect(matrix.total_queries > 0, content="true")
}

///|
test "all application scenarios" {
  inspect(run_application_scenarios().length(), content="3")
  inspect(all_application_scenarios_pass(), content="true")
}