package router

import "regexp"

// WorldQueryGrammars — stage-0 rules for the two question shapes that name the
// world in their own words, and say so plainly enough that no scorer is needed
// (V-655).
//
// They exist because of what happens when nothing deterministic claims these.
// Measured on the box on 2026-08-07 (docs/evals/2026-08-07-week-of-usage.md,
// section 4): "что такое TCP?" and "сколько будет 17 на 23?" were both answered
// "для какого города?", and "кто такой Линус Торвальдс?" was answered "не знаю —
// не нашла у тебя такой записи". None of those three is about him, about the
// weather, or about anything on this box.
//
// The mechanism is the destination, not the answer. Naming SourceWorld does not
// send the turn outside and does not skip a single source that looks something
// up: his notes, his facts and the personal boundary all still run first, in the
// order they always did. What it does is stop the sources that claim on seed
// similarity from taking the turn on the way past. Weather cannot claim a
// question about a protocol once the utterance has said which side it is on.
//
// Both patterns are spelled out here rather than drawn from internal/lexicon,
// which is the same call the agenda rules made: these are interrogative FRAMES
// of two words, not a closed class of single words, and the lexicon holds
// classes. Nothing here is a stem pattern over open vocabulary — the variable
// part of each rule is the topic, and the rule reads none of it.
func WorldQueryGrammars() []Grammar {
	return []Grammar{
		{
			// "что такое X", "кто такой X". A request for what a thing or a
			// person IS, which his own data can answer and usually cannot.
			//
			// The topic is deliberately not captured into Slots.Text. Every
			// source below reads the utterance, "что такое TCP?" is already the
			// best query string for it, and the agenda rules make the same call
			// for the same reason.
			Name:    "definition-query",
			Pattern: definitionQueryPattern,
			Build:   queryTo(SourceWorld),
		},
		{
			// "сколько будет 17 на 23", "сколько будет 2+2". Arithmetic, which
			// the metasearch answers and no local source holds. The digits are
			// what make it arithmetic: "сколько будет гостей" names no number
			// and is a question about his evening.
			Name:    "arithmetic-query",
			Pattern: arithmeticQueryPattern,
			Build:   queryTo(SourceWorld),
		},
		PublicCurrentVersionGrammar(),
	}
}

// WorldQueryDecision applies the literal world-side grammars outside the full
// cascade. It is used at defensive reconstruction boundaries (a question that
// reached the fact handler) so the same structural evidence can be restored
// without trusting the model that misrouted it.
func WorldQueryDecision(utterance string) (Decision, bool) {
	for _, grammar := range WorldQueryGrammars() {
		decision, _, accepted := grammar.Evaluate(utterance)
		if !accepted {
			continue
		}
		decision.Utterance = utterance
		decision.SourceAnchored = decision.Source != SourceUnknown
		return decision, true
	}
	return Decision{}, false
}

// definitionQueryPattern — anchored at the start, because "напомни узнать что
// такое TCP" is a reminder that happens to contain the frame.
//
// (\s|[?!.]|$) and not \b: Go's \b is ASCII-only and never fires after a
// Cyrillic letter, so the ASCII form silently matches nothing. The agenda rules
// carry the same note.
var definitionQueryPattern = regexp.MustCompile(
	`(?i)^\s*(что\s+так(ое|ая)|кто\s+так(ой|ая|ие)|what\s+is|who\s+is)(\s|[?!.]|$)`)

// arithmeticQueryPattern — the ask, then a digit somewhere after it. Loose on
// what sits between them on purpose: the operator is spoken half a dozen ways
// ("на", "умножить на", "плюс", "+") and reading them is the calculator's job,
// not this rule's. All this decides is which side of the boundary the turn is
// on.
var arithmeticQueryPattern = regexp.MustCompile(
	`(?i)^\s*(сколько\s+будет|посчитай|вычисли|how\s+much\s+is)\s.*\d`)

// queryTo builds a stage-0 query Decision that names where the answer lives.
//
// The utterance travels intact and no slot is filled, which is the same
// contract agendaQueryBuild has: confidence 1.0 on the intent and the
// destination, and every source below still decides for itself whether it has
// an answer. Naming a destination narrows who may guess. It promises nothing.
func queryTo(dest Source) func([]string) (Decision, bool) {
	return func([]string) (Decision, bool) {
		return Decision{
			Stage:      0,
			Intent:     IntentQuery,
			Confidence: 1.0,
			Source:     dest,
		}, true
	}
}
