package router

// StageZeroGrammars — the stage 0 rule set, in the order the daemon runs it.
//
// It lives here because it used to live in two places (V-693). `buildRouter` in
// cmd/mavend/voicewire.go held the real set and `baselineGrammars` in
// internal/router/eval/eval_test.go restated it by hand, in the daemon's order,
// with its own comment saying so. Three test files score against the fixture,
// and nothing compared the two lists. By 2026-08-11 they had already drifted:
// BareCaptureGrammar was in the daemon and not in the fixture, so every routing
// measurement scored a set nobody ran. That is the failure CLAUDE.md warned
// about by name, and a diff test would have caught it one grammar late. One
// list cannot drift from itself.
//
// The order is the contract, not the membership. Each rule below says why it
// sits where it sits, and a rule inserted in the wrong place changes which
// utterances the cascade never reaches. Read the comment above a line before
// moving it, and read docs/routing.md before adding one.
//
// The classifier, the extractor, the threshold and the model arm are the
// daemon's to assemble. This function returns the rules and nothing else, so
// the fixture can also run them one at a time and see which of them contend for
// the same utterance, which the cascade hides by stopping at the first match.
func StageZeroGrammars(acts ActMatcher) []Grammar {
	// Negative authority gets first refusal. This structural grammar reads the
	// command frame, not a substring, and emits a non-executable sentinel. It
	// must precede the wake-word allowlist fast path: an unusually permissive
	// matcher may recognise the verb inside "Maven, don't restart nginx", but
	// an allowlist match cannot turn an explicit prohibition into permission.
	grammars := []Grammar{CommandProhibitionGrammar()}
	grammars = append(grammars, DefaultGrammars(acts)...)
	// A refusal, not a guessed intent. It is safe ahead of every positive rule
	// because it accepts only filler plus unresolved-reference words.
	grammars = append(grammars, AmbiguousFragmentGrammar())
	grammars = append(grammars, SystemTimeDateGrammars()...)
	// After the time/date rules on purpose: "какой сегодня день" is a clock
	// question and must keep reaching replySystem, while "что у меня сегодня"
	// is an agenda question and must not.
	grammars = append(grammars, AgendaQueryGrammars()...)
	// Russian can ask "how long since" through word order rather than a
	// question word. This must land in recall before a statistical head reads
	// the same past-tense verb as a fact.
	grammars = append(grammars, ImplicitElapsedQueryGrammar())
	// Same reason as the agenda rules, for the feeds: "что нового в лентах?"
	// routed system and answered "пока не умею" (Vikunja #474).
	// After the agenda rules, which are the narrower claim, and BEFORE the feed
	// and list rules, which are not: "что такое лента" is a definition question
	// and the feed rule would take it on the noun alone (V-655).
	grammars = append(grammars, WorldQueryGrammars()...)
	// Questions about operating Maven herself are local product help. They sit
	// beside the world anchors because both decide which side of the personal
	// boundary owns an answer, in opposite directions.
	grammars = append(grammars, MavenHelpGrammar())
	grammars = append(grammars, FeedQueryGrammar())
	// The list side of the same exposure: a phrasing with no possessive in it
	// ("список дел") routed system and never reached queryTasks (Vikunja #467).
	grammars = append(grammars, TaskListGrammar())
	grammars = append(grammars, ListGrammars()...)
	grammars = append(grammars, ReminderGrammar())
	// A first-person report about a cancellation is neither another mutation
	// nor a memory query. The imperative cancellation pre-route has already had
	// first refusal before the router runs.
	grammars = append(grammars, ReminderCancellationReportGrammar())
	// Before the capture marker, because "отметь" is a capture verb and "отметь
	// второй пункт" is not a note. The Praxis rules are the narrower claim — a
	// lifecycle verb AND an item named — so they get first refusal (Vikunja #516).
	grammars = append(grammars, PraxisGrammars()...)
	// After Praxis, whose bare "закрой" claim this rule cannot reach (it needs the
	// board noun), and before the capture marker, which would otherwise read
	// "убери из задач купить молоко" as a new task (Vikunja #512).
	grammars = append(grammars, TaskStatusGrammar())
	// Before the capture markers, which all need an object. A capture verb
	// alone is a fact with no key, and the clarify path asks for it rather than
	// letting the model invent an answer (Vikunja #557).
	grammars = append(grammars, BareCaptureGrammar()...)
	// Last, and it matches any utterance shape — its Build is the filter. An
	// explicit capture marker beats the model, which called it an act and
	// rewrote the task text (Vikunja #467). After the rules above because a
	// marker never collides with a clock or agenda question.
	grammars = append(grammars, TaskCaptureGrammar())
	// After the capture marker, so "запиши" still wins over "расскажи", and
	// last overall because it matches on the first word alone: "расскажи про
	// X" is a world question the model called a fact (Vikunja #498).
	grammars = append(grammars, NarrativeQueryGrammars()...)
	// Last because it is deliberately broad over the open remainder of a
	// grammatical declaration. Every explicit question, command, capture and
	// narrative request above gets first refusal; this catches the Russian
	// present-tense possession statement the statistical floors call a query.
	grammars = append(grammars, PossessionStatementGrammar())
	return grammars
}
