package analytics

import (
	"context"
	"strings"
	"testing"

	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/metadata"
	"github.com/nexuscrm/metadata/catalogsnapshot"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/formulastorage"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestComputeValidationProjectionClosure_NarrowsToReferencedFields(t *testing.T) {
	mode := "active"
	rt := models.FieldType(constants.FieldTypeString)

	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{APIName: "Name", Type: constants.FieldTypeString},
			{APIName: "Status__c", Type: constants.FieldTypeString},
			{
				APIName:     "FormulaA__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Name"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "FormulaB__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Status__c"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "FormulaC__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Id"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "FormulaD__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("FormulaA__c"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
		},
	}

	rules := []*models.ValidationRule{
		{
			Active:    true,
			Condition: "FormulaA__c != null",
		},
	}

	defs := []FormulaDefinition{
		{Name: "FormulaA__c", Expression: "Name", ExtraDeps: nil},
		{Name: "FormulaB__c", Expression: "Status__c", ExtraDeps: nil},
		{Name: "FormulaC__c", Expression: "Id", ExtraDeps: nil},
		{Name: "FormulaD__c", Expression: "FormulaA__c", ExtraDeps: []string{"FormulaA__c"}},
	}

	svc := &FormulaViewService{
		validationProjection: newValidationProjectionCache(),
	}

	// Directly test the closure computation logic
	directRefs := extractValidationRuleFieldRefs(rules, schema)

	// Only FormulaA__c should be in direct refs (it's referenced in the condition)
	if !directRefs["FormulaA__c"] {
		t.Error("expected FormulaA__c in direct refs")
	}
	if directRefs["FormulaB__c"] || directRefs["FormulaC__c"] || directRefs["FormulaD__c"] {
		t.Error("expected only FormulaA__c in direct refs, got others")
	}

	// Now test the full closure which should include FormulaA__c only
	// (it has no ExtraDeps that are active-computed)
	svc.computeValidationProjectionClosureFromRules(schema, defs, rules)
	fields, ok := svc.validationProjection.get("Account")
	if !ok {
		t.Fatal("expected validation projection to be computed")
	}
	if len(fields) != 1 {
		t.Fatalf("expected 1 field in closure, got %d: %v", len(fields), fields)
	}
	if fields[0] != "FormulaA__c" {
		t.Errorf("expected FormulaA__c in closure, got %s", fields[0])
	}
}

func TestComputeValidationProjectionClosure_HandlesRuntimeFormulaSyntax(t *testing.T) {
	mode := "active"
	rt := models.FieldType(constants.FieldTypeString)

	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{APIName: "Name", Type: constants.FieldTypeString},
			{
				APIName:     "FormulaA__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Name"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "FormulaB__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Id"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "FormulaC__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Id"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "FormulaD__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Id"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "Unrelated__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Id"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
		},
	}

	// Tests all runtime formula syntax forms that reference active computed
	// fields: $Profile alongside bare ref, $Record.Field, PRIORVALUE(Field),
	// record.Field, and prior.Field. None use ExtraDeps — these are the
	// production paths the closure must handle purely from condition parsing.
	rules := []*models.ValidationRule{
		{
			Active:    true,
			Condition: `AND($Profile.Name == "Sales", FormulaA__c > 0)`,
		},
		{
			Active:    true,
			Condition: `$Record.FormulaB__c != null`,
		},
		{
			Active:    true,
			Condition: `PRIORVALUE(FormulaC__c) != FormulaC__c`,
		},
		{
			Active:    true,
			Condition: `record.FormulaD__c == "test"`,
		},
	}

	defs := []FormulaDefinition{
		{Name: "FormulaA__c", Expression: "Name", ExtraDeps: nil},
		{Name: "FormulaB__c", Expression: "Id", ExtraDeps: nil},
		{Name: "FormulaC__c", Expression: "Id", ExtraDeps: nil},
		{Name: "FormulaD__c", Expression: "Id", ExtraDeps: nil},
		{Name: "Unrelated__c", Expression: "Id", ExtraDeps: nil},
	}

	svc := &FormulaViewService{
		validationProjection: newValidationProjectionCache(),
	}

	svc.computeValidationProjectionClosureFromRules(schema, defs, rules)
	fields, ok := svc.validationProjection.get("Account")
	if !ok {
		t.Fatal("expected validation projection to be computed")
	}

	fieldSet := make(map[string]bool)
	for _, f := range fields {
		fieldSet[f] = true
	}

	if !fieldSet["FormulaA__c"] {
		t.Error("expected FormulaA__c in closure (bare ref alongside $Profile)")
	}
	if !fieldSet["FormulaB__c"] {
		t.Error("expected FormulaB__c in closure ($Record.FormulaB__c)")
	}
	if !fieldSet["FormulaC__c"] {
		t.Error("expected FormulaC__c in closure (PRIORVALUE + bare ref)")
	}
	if !fieldSet["FormulaD__c"] {
		t.Error("expected FormulaD__c in closure (record.FormulaD__c)")
	}
	if fieldSet["Unrelated__c"] {
		t.Error("Unrelated__c should NOT be in closure")
	}
}

func TestComputeValidationProjectionClosure_IncludesRelationFKDeps(t *testing.T) {
	mode := "active"
	rt := models.FieldType(constants.FieldTypeString)
	relName := "PrimaryContact"

	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{APIName: "Name", Type: constants.FieldTypeString},
			{
				APIName:          "PrimaryContact__c",
				Type:             constants.FieldTypeFormula,
				Formula:          strPtr("Name"),
				FormulaMode:      &mode,
				ReturnType:       &rt,
				RelationshipName: &relName,
				ReferenceTo:      []string{"Contact"},
			},
			{
				APIName:     "FormulaA__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Name"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
		},
	}

	// Rules reference the FK field only through relationship traversals
	// (PrimaryContact.Name and $Record.PrimaryContact.Name) with NO ExtraDeps.
	// The closure must resolve the relationship name to the FK field.
	rules := []*models.ValidationRule{
		{
			Active:    true,
			Condition: `PrimaryContact.Name != null`,
		},
		{
			Active:    true,
			Condition: `FormulaA__c > 0`,
		},
	}

	defs := []FormulaDefinition{
		{Name: "PrimaryContact__c", Expression: "Name", ExtraDeps: nil},
		{Name: "FormulaA__c", Expression: "Name", ExtraDeps: nil},
	}

	svc := &FormulaViewService{
		validationProjection: newValidationProjectionCache(),
	}

	svc.computeValidationProjectionClosureFromRules(schema, defs, rules)
	fields, ok := svc.validationProjection.get("Account")
	if !ok {
		t.Fatal("expected validation projection to be computed")
	}

	fieldSet := make(map[string]bool)
	for _, f := range fields {
		fieldSet[f] = true
	}

	if !fieldSet["PrimaryContact__c"] {
		t.Error("expected PrimaryContact__c in closure (relationship PrimaryContact → FK PrimaryContact__c)")
	}
	if !fieldSet["FormulaA__c"] {
		t.Error("expected FormulaA__c in closure (direct bare ref)")
	}
}

func TestComputeValidationProjectionClosure_IncludesChainedExpressionDeps(t *testing.T) {
	mode := "active"
	rt := models.FieldType(constants.FieldTypeString)

	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{APIName: "Name", Type: constants.FieldTypeString},
			{
				APIName:     "FormulaA__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Name"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "FormulaB__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("FormulaA__c"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "FormulaC__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("FormulaB__c"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "Unrelated__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Id"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
		},
	}

	// Rule references FormulaC__c which depends on FormulaB__c via Expression,
	// which in turn depends on FormulaA__c via Expression. NO ExtraDeps are
	// populated — this is the production path where bare FieldRef dependencies
	// between active computed fields must be followed transitively.
	rules := []*models.ValidationRule{
		{
			Active:    true,
			Condition: "FormulaC__c != null",
		},
	}

	defs := []FormulaDefinition{
		{Name: "FormulaA__c", Expression: "Name", ExtraDeps: nil},
		{Name: "FormulaB__c", Expression: "FormulaA__c", ExtraDeps: nil},
		{Name: "FormulaC__c", Expression: "FormulaB__c", ExtraDeps: nil},
		{Name: "Unrelated__c", Expression: "Id", ExtraDeps: nil},
	}

	svc := &FormulaViewService{
		validationProjection: newValidationProjectionCache(),
	}

	svc.computeValidationProjectionClosureFromRules(schema, defs, rules)
	fields, ok := svc.validationProjection.get("Account")
	if !ok {
		t.Fatal("expected validation projection to be computed")
	}

	fieldSet := make(map[string]bool)
	for _, f := range fields {
		fieldSet[f] = true
	}

	if !fieldSet["FormulaC__c"] {
		t.Error("expected FormulaC__c in closure (directly referenced by rule)")
	}
	if !fieldSet["FormulaB__c"] {
		t.Error("expected FormulaB__c in closure (expression dep of FormulaC__c)")
	}
	if !fieldSet["FormulaA__c"] {
		t.Error("expected FormulaA__c in closure (expression dep of FormulaB__c)")
	}
	if fieldSet["Unrelated__c"] {
		t.Error("Unrelated__c should NOT be in closure")
	}
	if len(fields) != 3 {
		t.Errorf("expected exactly 3 fields in closure, got %d: %v", len(fields), fields)
	}
}

// TestComputeValidationProjectionClosure_IncludesFormulaCrossRefFKDeps covers the
// issue #2298 current-head Codex blocker: an active computed reference/lookup
// field consumed ONLY through another active formula's relationship traversal
// (a CrossRef like ComputedLookup.Name) must still land in the closure. The
// validation rule references only the consumer formula, so the FK dependency is
// reachable solely through the formula-to-formula expression-dep map, not the
// rule's direct refs. Before the fix buildExpressionDeps only followed bare
// FieldRefs and dropped the CrossRef root, so the FK was omitted and validation
// would read it as nil from the raw record.
func TestComputeValidationProjectionClosure_IncludesFormulaCrossRefFKDeps(t *testing.T) {
	mode := "active"
	rt := models.FieldType(constants.FieldTypeString)
	relName := "ComputedLookup"

	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{APIName: "Name", Type: constants.FieldTypeString},
			{
				APIName:          "ComputedLookup__c",
				Type:             constants.FieldTypeFormula,
				Formula:          strPtr("Name"),
				FormulaMode:      &mode,
				ReturnType:       &rt,
				RelationshipName: &relName,
				ReferenceTo:      []string{"Contact"},
			},
			{
				APIName:     "ComputedLookupName__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("ComputedLookup.Name"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "Unrelated__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Id"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
		},
	}

	// Rule references ONLY the consumer formula. The FK dependency
	// (ComputedLookup__c) must be pulled in transitively via the consumer's
	// CrossRef traversal, with NO ExtraDeps populated.
	rules := []*models.ValidationRule{
		{
			Active:    true,
			Condition: "ComputedLookupName__c != null",
		},
	}

	defs := []FormulaDefinition{
		{Name: "ComputedLookup__c", Expression: "Name", ExtraDeps: nil},
		{Name: "ComputedLookupName__c", Expression: "ComputedLookup.Name", ExtraDeps: nil},
		{Name: "Unrelated__c", Expression: "Id", ExtraDeps: nil},
	}

	svc := &FormulaViewService{
		validationProjection: newValidationProjectionCache(),
	}

	svc.computeValidationProjectionClosureFromRules(schema, defs, rules)
	fields, ok := svc.validationProjection.get("Account")
	if !ok {
		t.Fatal("expected validation projection to be computed")
	}

	fieldSet := make(map[string]bool)
	for _, f := range fields {
		fieldSet[f] = true
	}

	if !fieldSet["ComputedLookupName__c"] {
		t.Error("expected ComputedLookupName__c in closure (directly referenced by rule)")
	}
	if !fieldSet["ComputedLookup__c"] {
		t.Error("expected ComputedLookup__c FK in closure (CrossRef root ComputedLookup → FK ComputedLookup__c, consumed only via ComputedLookupName__c)")
	}
	if fieldSet["Unrelated__c"] {
		t.Error("Unrelated__c should NOT be in closure")
	}
}

func TestComputeValidationProjectionClosure_IncludesRecordPathBindingDeps(t *testing.T) {
	mode := "active"
	rt := models.FieldType(constants.FieldTypeString)

	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{APIName: "Name", Type: constants.FieldTypeString},
			{
				APIName:     "FormulaA__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Name"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "FormulaB__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Id"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
		},
	}

	// Rule uses a record-set binding with recordPath referencing an active
	// computed field. The condition references FormulaA__c (to make the closure
	// non-empty), and the binding references FormulaB__c via $Record. prefix.
	rules := []*models.ValidationRule{
		{
			Active:    true,
			Condition: "FormulaA__c != null",
			RecordSetBindings: []models.ValidationRuleRecordSetBinding{
				{
					Name:          "TestBinding",
					Mode:          "exists",
					ObjectAPIName: "Contact",
					Filters: []models.ValidationRuleRecordSetFilter{
						{
							Field:       "AccountId",
							Operator:    "equals",
							ValueSource: "recordPath",
							Value:       "$Record.FormulaB__c",
						},
					},
				},
			},
		},
	}

	defs := []FormulaDefinition{
		{Name: "FormulaA__c", Expression: "Name", ExtraDeps: nil},
		{Name: "FormulaB__c", Expression: "Id", ExtraDeps: nil},
	}

	svc := &FormulaViewService{
		validationProjection: newValidationProjectionCache(),
	}

	svc.computeValidationProjectionClosureFromRules(schema, defs, rules)
	fields, ok := svc.validationProjection.get("Account")
	if !ok {
		t.Fatal("expected validation projection to be computed")
	}

	fieldSet := make(map[string]bool)
	for _, f := range fields {
		fieldSet[f] = true
	}

	if !fieldSet["FormulaA__c"] {
		t.Error("expected FormulaA__c in closure (directly referenced in condition)")
	}
	if !fieldSet["FormulaB__c"] {
		t.Error("expected FormulaB__c in closure (referenced via $Record.FormulaB__c recordPath binding)")
	}
	if len(fields) != 2 {
		t.Errorf("expected exactly 2 fields in closure, got %d: %v", len(fields), fields)
	}
}

func TestComputeValidationProjectionClosure_IncludesExtraDeps(t *testing.T) {
	mode := "active"
	rt := models.FieldType(constants.FieldTypeString)

	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{APIName: "Name", Type: constants.FieldTypeString},
			{
				APIName:     "DisplayName__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Name"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "FullTitle__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Name"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "Unreferenced__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Id"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
		},
	}

	rules := []*models.ValidationRule{
		{
			Active:    true,
			Condition: "FullTitle__c = 'test'",
		},
	}

	// FullTitle__c has an ExtraDep on DisplayName__c (simulating a Name-rewrite dep)
	defs := []FormulaDefinition{
		{Name: "DisplayName__c", Expression: "Name", ExtraDeps: nil},
		{Name: "FullTitle__c", Expression: "Name", ExtraDeps: []string{"DisplayName__c"}},
		{Name: "Unreferenced__c", Expression: "Id", ExtraDeps: nil},
	}

	svc := &FormulaViewService{
		validationProjection: newValidationProjectionCache(),
	}

	svc.computeValidationProjectionClosureFromRules(schema, defs, rules)
	fields, ok := svc.validationProjection.get("Account")
	if !ok {
		t.Fatal("expected validation projection to be computed")
	}

	fieldSet := make(map[string]bool)
	for _, f := range fields {
		fieldSet[f] = true
	}

	// FullTitle__c is directly referenced, DisplayName__c is an ExtraDep
	if !fieldSet["FullTitle__c"] {
		t.Error("expected FullTitle__c in closure (directly referenced)")
	}
	if !fieldSet["DisplayName__c"] {
		t.Error("expected DisplayName__c in closure (ExtraDep of FullTitle__c)")
	}
	if fieldSet["Unreferenced__c"] {
		t.Error("Unreferenced__c should NOT be in closure")
	}
	if len(fields) != 2 {
		t.Errorf("expected exactly 2 fields in closure, got %d: %v", len(fields), fields)
	}
}

func TestComputeValidationProjectionClosure_NoRulesRemovesCache(t *testing.T) {
	mode := "active"
	rt := models.FieldType(constants.FieldTypeString)

	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{
				APIName:     "FormulaA__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Id"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
		},
	}

	svc := &FormulaViewService{
		validationProjection: newValidationProjectionCache(),
	}

	// Pre-populate
	c := svc.validationProjection
	c.setIfGeneration("Account", []string{"FormulaA__c"}, c.currentGeneration())

	// No rules → closure should be removed
	svc.computeValidationProjectionClosureFromRules(schema, nil, nil)
	_, ok := svc.validationProjection.get("Account")
	if ok {
		t.Error("expected closure to be removed when no rules exist")
	}
}

func TestComputeValidationProjectionClosure_InactiveRulesIgnored(t *testing.T) {
	mode := "active"
	rt := models.FieldType(constants.FieldTypeString)

	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{
				APIName:     "FormulaA__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Id"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
		},
	}

	rules := []*models.ValidationRule{
		{
			Active:    false,
			Condition: "FormulaA__c != null",
		},
	}

	defs := []FormulaDefinition{
		{Name: "FormulaA__c", Expression: "Id", ExtraDeps: nil},
	}

	svc := &FormulaViewService{
		validationProjection: newValidationProjectionCache(),
	}

	svc.computeValidationProjectionClosureFromRules(schema, defs, rules)
	_, ok := svc.validationProjection.get("Account")
	if ok {
		t.Error("expected no closure when all rules are inactive")
	}
}

func TestValidationActiveComputedFields_UsesClosureWhenAvailable(t *testing.T) {
	mode := "active"
	rt := models.FieldType(constants.FieldTypeString)

	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{
				APIName:     "FormulaA__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Id"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "FormulaB__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Id"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "FormulaC__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Id"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
		},
	}

	svc := &FormulaViewService{
		validationProjection: newValidationProjectionCache(),
	}
	c := svc.validationProjection
	c.setIfGeneration("Account", []string{"FormulaA__c"}, c.currentGeneration())

	fields, ok := svc.ValidationProjectionFields("Account")
	if !ok {
		t.Fatal("expected projection fields to be available")
	}
	if len(fields) != 1 || fields[0] != "FormulaA__c" {
		t.Errorf("expected [FormulaA__c], got %v", fields)
	}

	// Verify the provider interface works for narrowing
	_ = schema // used above to verify the test setup is consistent
}

// TestValidationProjectionCache_StaleRebuildGuard proves that a rebuild which
// captured a generation BEFORE an invalidation cannot repopulate the cache
// after that invalidation. This is the stale-in-flight-rebuild race: a rebuild
// reads schema/defs at generation N, then RefreshCache/CRUD invalidation bumps
// the generation to N+1 and clears the entry; when the stale rebuild finally
// stores, setIfGeneration must drop the write because N != N+1. A silent
// repopulation here would let validationActiveComputedFields narrow against a
// stale closure and false-exclude fields — a hard_red_line correctness flip.
func TestValidationProjectionCache_StaleRebuildGuard(t *testing.T) {
	c := newValidationProjectionCache()

	// A rebuild begins and captures the current generation.
	staleGen := c.currentGeneration()

	// An invalidation (RefreshCache / validation-rule CRUD) lands while the
	// rebuild is still in flight, bumping the generation.
	c.clear()

	// The stale rebuild now tries to store its (old-metadata) closure.
	c.setIfGeneration("Account", []string{"StaleField__c"}, staleGen)

	if _, ok := c.get("Account"); ok {
		t.Fatal("stale rebuild repopulated the cache after invalidation; setIfGeneration must drop writes whose generation moved")
	}

	// A fresh rebuild that captures the post-invalidation generation stores fine.
	freshGen := c.currentGeneration()
	c.setIfGeneration("Account", []string{"FreshField__c"}, freshGen)

	fields, ok := c.get("Account")
	if !ok {
		t.Fatal("expected fresh rebuild (current generation) to store successfully")
	}
	if len(fields) != 1 || fields[0] != "FreshField__c" {
		t.Errorf("expected [FreshField__c], got %v", fields)
	}
}

// TestComputeValidationProjectionClosureFromRulesWithGen_StaleGenDropsStore
// proves the integration seam: a rebuild that captured a pre-invalidation
// generation and then computes a non-empty closure must NOT store it once the
// generation has moved. This is the path RebuildView takes — it captures the
// generation before reading the schema and threads it down to the store.
func TestComputeValidationProjectionClosureFromRulesWithGen_StaleGenDropsStore(t *testing.T) {
	mode := "active"
	rt := models.FieldType(constants.FieldTypeString)

	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{
				APIName:     "FormulaA__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Id"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
		},
	}
	rules := []*models.ValidationRule{
		{Active: true, Condition: "FormulaA__c != null"},
	}
	defs := []FormulaDefinition{
		{Name: "FormulaA__c", Expression: "Id", ExtraDeps: nil},
	}

	svc := &FormulaViewService{
		validationProjection: newValidationProjectionCache(),
	}

	// Rebuild captures the generation before the schema read.
	staleGen := svc.projectionGeneration()

	// A concurrent invalidation lands mid-rebuild, bumping the generation.
	svc.validationProjection.clear()

	// The rebuild finishes computing a non-empty closure and tries to store it
	// with the now-stale generation. It must be dropped.
	svc.computeValidationProjectionClosureFromRulesWithGen(schema, defs, rules, staleGen)

	if _, ok := svc.validationProjection.get("Account"); ok {
		t.Fatal("stale-generation rebuild stored a closure after invalidation; runtime would narrow against a stale set and false-exclude fields")
	}
}

// projectionRehydrationRepo serves a schema plus its validation rules so the
// MetadataService cache exposes both GetSchemas and GetValidationRules, the two
// inputs RecomputeAllValidationProjections reads.
type projectionRehydrationRepo struct {
	ports.MetadataRepository
	schemas []*models.ObjectMetadata
	rules   []*models.ValidationRule
}

func (r *projectionRehydrationRepo) GetAllSchemas(context.Context) ([]*models.ObjectMetadata, error) {
	return r.schemas, nil
}

func (r *projectionRehydrationRepo) GetSchemaByAPIName(_ context.Context, apiName string) (*models.ObjectMetadata, error) {
	for _, s := range r.schemas {
		if s != nil && strings.EqualFold(s.APIName, apiName) {
			return s, nil
		}
	}
	return nil, nil
}

func (r *projectionRehydrationRepo) GetAllRelationships(context.Context) ([]*models.Relationship, error) {
	return nil, nil
}

func (r *projectionRehydrationRepo) GetAllFlowDefinitions(context.Context) ([]*models.FlowDefinition, error) {
	return nil, nil
}

func (r *projectionRehydrationRepo) GetAllValidationRules(context.Context) ([]*models.ValidationRule, error) {
	return r.rules, nil
}

// TestRecomputeAllValidationProjections_RehydratesAfterInvalidation covers the
// issue #2298 current-head Codex blocker: the projection cache is only populated
// as a side effect of RebuildView (DDL), but the canonical apply flow ends with a
// RefreshCache that invalidates it, and a plain restart starts empty and never
// rebuilds views. RecomputeAllValidationProjections must rebuild the closure from
// the current schema + rule snapshot WITHOUT DDL, so the optimization survives
// apply/refresh/restart instead of silently falling back to all active fields.
func TestRecomputeAllValidationProjections_RehydratesAfterInvalidation(t *testing.T) {
	mode := "active"
	rt := models.FieldType(constants.FieldTypeString)

	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{APIName: "Name", Type: constants.FieldTypeString},
			{
				APIName:     "FormulaA__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Name"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "FormulaB__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Id"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
		},
	}

	rules := []*models.ValidationRule{
		{ObjectAPIName: "Account", Active: true, Condition: "FormulaA__c != null"},
	}

	metaSvc := metadata.NewMetadataService(&projectionRehydrationRepo{
		schemas: []*models.ObjectMetadata{schema},
		rules:   rules,
	}, nil, nil)
	if err := metaSvc.RefreshCache(context.Background()); err != nil {
		t.Fatalf("RefreshCache() error = %v", err)
	}

	svc := &FormulaViewService{
		metadata:             metaSvc,
		validationProjection: newValidationProjectionCache(),
	}

	// Initial rehydration (mirrors startup after RefreshMetadataCache).
	if err := svc.RecomputeAllValidationProjections(context.Background()); err != nil {
		t.Fatalf("RecomputeAllValidationProjections() error = %v", err)
	}
	fields, ok := svc.validationProjection.get("Account")
	if !ok {
		t.Fatal("expected projection populated after recompute; runtime would fall back to all active fields")
	}
	if len(fields) != 1 || fields[0] != "FormulaA__c" {
		t.Fatalf("expected closure [FormulaA__c], got %v", fields)
	}

	// Simulate the apply/refresh hook clearing the cache (RefreshCache ->
	// InvalidateAllValidationProjections).
	svc.InvalidateAllValidationProjections()
	if _, ok := svc.validationProjection.get("Account"); ok {
		t.Fatal("expected cache cleared after invalidation")
	}

	// The post-refresh / restart recompute must restore the closure.
	if err := svc.RecomputeAllValidationProjections(context.Background()); err != nil {
		t.Fatalf("RecomputeAllValidationProjections() (rehydrate) error = %v", err)
	}
	fields, ok = svc.validationProjection.get("Account")
	if !ok {
		t.Fatal("expected projection rehydrated after invalidation; optimization would be absent on apply/restart paths")
	}
	if len(fields) != 1 || fields[0] != "FormulaA__c" {
		t.Fatalf("expected rehydrated closure [FormulaA__c], got %v", fields)
	}
}

// TestRecomputeAllValidationProjections_StaleEpochCannotOverwriteFresh covers the
// issue #2298 round-15 Codex blocker: RecomputeAllValidationProjections captures
// the projection generation BEFORE its first metadata read, so an invalidation
// (or a concurrent RebuildView publishing a fresh closure) that lands after the
// snapshot bumps the generation and forces the stale recompute's stores to drop
// rather than overwrite fresh state or mix old defs with newly-read rules.
//
// The race is simulated deterministically at the store seam the recompute uses:
// capture the epoch, let an invalidation + fresh publish land, then drive the
// stale-epoch store and prove it cannot clobber the fresh closure.
func TestRecomputeAllValidationProjections_StaleEpochCannotOverwriteFresh(t *testing.T) {
	mode := "active"
	rt := models.FieldType(constants.FieldTypeString)

	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{APIName: "Name", Type: constants.FieldTypeString},
			{
				APIName:     "FormulaA__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Name"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
			{
				APIName:     "FormulaB__c",
				Type:        constants.FieldTypeFormula,
				Formula:     strPtr("Id"),
				FormulaMode: &mode,
				ReturnType:  &rt,
			},
		},
	}

	// Stale-epoch defs/rules: only FormulaA__c referenced.
	staleRules := []*models.ValidationRule{
		{ObjectAPIName: "Account", Active: true, Condition: "FormulaA__c != null"},
	}
	staleDefs := []FormulaDefinition{
		{Name: "FormulaA__c", Expression: "Name"},
		{Name: "FormulaB__c", Expression: "Id"},
	}

	svc := &FormulaViewService{validationProjection: newValidationProjectionCache()}

	// Recompute captures the generation before its metadata read.
	staleGen := svc.projectionGeneration()

	// After that snapshot, metadata changes invalidate the cache and a fresh
	// rebuild (new epoch) publishes the up-to-date closure — here a wider set
	// reflecting a rule that now also references FormulaB__c.
	svc.validationProjection.clear()
	freshRules := []*models.ValidationRule{
		{ObjectAPIName: "Account", Active: true, Condition: "AND(FormulaA__c != null, FormulaB__c != null)"},
	}
	svc.computeValidationProjectionClosureFromRules(schema, staleDefs, freshRules)

	freshBefore, ok := svc.validationProjection.get("Account")
	if !ok || len(freshBefore) != 2 {
		t.Fatalf("expected fresh closure [FormulaA__c, FormulaB__c] published, got %v (ok=%v)", freshBefore, ok)
	}

	// The stale-epoch recompute now tries to store its narrower closure under the
	// generation it captured before the invalidation. It must be dropped.
	svc.computeValidationProjectionClosureFromRulesWithGen(schema, staleDefs, staleRules, staleGen)

	freshAfter, ok := svc.validationProjection.get("Account")
	if !ok {
		t.Fatal("fresh closure was removed by a stale-epoch recompute")
	}
	if len(freshAfter) != 2 {
		t.Fatalf("stale-epoch recompute overwrote fresh closure: got %v, want [FormulaA__c, FormulaB__c]", freshAfter)
	}
}

// TestProjectionEpochHelpers_CaptureGenerationBeforeRead asserts the structural
// invariant that closes the generation-ordering bug class for good: the epoch
// helpers capture the projection generation BEFORE reading metadata, so the
// generation they return predates any invalidation that lands after the read.
// Every projection publish path obtains its (generation, schema) pair through
// these helpers, so no present or future call site can reverse the order.
func TestProjectionEpochHelpers_CaptureGenerationBeforeRead(t *testing.T) {
	mode := "active"
	rt := models.FieldType(constants.FieldTypeString)
	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{APIName: "Name", Type: constants.FieldTypeString},
			{APIName: "FormulaA__c", Type: constants.FieldTypeFormula, Formula: strPtr("Name"), FormulaMode: &mode, ReturnType: &rt},
		},
	}
	metaSvc := metadata.NewMetadataService(&projectionRehydrationRepo{
		schemas: []*models.ObjectMetadata{schema},
		rules:   []*models.ValidationRule{{ObjectAPIName: "Account", Active: true, Condition: "FormulaA__c != null"}},
	}, nil, nil)
	if err := metaSvc.RefreshCache(context.Background()); err != nil {
		t.Fatalf("RefreshCache() error = %v", err)
	}
	svc := &FormulaViewService{metadata: metaSvc, validationProjection: newValidationProjectionCache()}

	// Advance the generation so we are not comparing against the zero value.
	svc.validationProjection.clear()
	svc.validationProjection.clear()
	wantGen := svc.projectionGeneration()

	genObj, gotSchema := svc.projectionEpochForObject(context.Background(), "Account")
	if genObj != wantGen {
		t.Fatalf("projectionEpochForObject gen = %d, want %d (generation must be captured at entry)", genObj, wantGen)
	}
	if gotSchema == nil || gotSchema.APIName != "Account" {
		t.Fatalf("projectionEpochForObject schema = %v, want Account", gotSchema)
	}

	genAll, schemas := svc.projectionEpochForAll(context.Background())
	if genAll != wantGen {
		t.Fatalf("projectionEpochForAll gen = %d, want %d", genAll, wantGen)
	}
	if len(schemas) != 1 {
		t.Fatalf("projectionEpochForAll schemas len = %d, want 1", len(schemas))
	}

	// An invalidation after the epoch capture must make the captured generation
	// stale, so a store attempted with it drops (fall-back-to-all-fields), never
	// publishes. This is the property every publish path relies on.
	svc.validationProjection.clear()
	svc.validationProjection.setIfGeneration("Account", []string{"FormulaA__c"}, genObj)
	if _, ok := svc.validationProjection.get("Account"); ok {
		t.Fatal("store with pre-invalidation epoch generation must drop, but it published")
	}
}

// TestRecomputeAllValidationProjections_EmptyObjectDoesNotSuppressLaterObjects
// covers the issue #2298 round-16 Codex blocker: RecomputeAllValidationProjections
// captures one generation for the whole batch, but the per-object closure compute
// deletes the entry for objects with no rules / empty closure. If that delete
// advanced the shared generation (the old remove()), every later object's
// generation-guarded store in the same batch would drop, leaving a large slice of
// the org un-rehydrated and falling back to all active fields. The same-epoch
// delete (removeIfGeneration) must not advance the generation, so a closure-less
// object cannot suppress rehydration of subsequent objects regardless of order.
func TestRecomputeAllValidationProjections_EmptyObjectDoesNotSuppressLaterObjects(t *testing.T) {
	mode := "active"
	rt := models.FieldType(constants.FieldTypeString)

	mkSchema := func(name string) *models.ObjectMetadata {
		return &models.ObjectMetadata{
			APIName: name,
			Fields: []models.FieldMetadata{
				{APIName: "Id", Type: constants.FieldTypeString},
				{APIName: "Name", Type: constants.FieldTypeString},
				{APIName: "FormulaA__c", Type: constants.FieldTypeFormula, Formula: strPtr("Name"), FormulaMode: &mode, ReturnType: &rt},
			},
		}
	}

	// Several objects, each with active computed fields. Only one of them has a
	// validation rule referencing a formula; the rest have no rules, so their
	// per-object compute takes the removeIfGeneration (no-rules) path. With many
	// no-rule objects, a generation-advancing delete would almost certainly fire
	// before the rule-bearing object on at least one map-iteration order.
	withRule := "Obj3__c"
	var schemas []*models.ObjectMetadata
	for _, n := range []string{"Obj1__c", "Obj2__c", withRule, "Obj4__c", "Obj5__c"} {
		schemas = append(schemas, mkSchema(n))
	}

	metaSvc := metadata.NewMetadataService(&projectionRehydrationRepo{
		schemas: schemas,
		rules: []*models.ValidationRule{
			{ObjectAPIName: withRule, Active: true, Condition: "FormulaA__c != null"},
		},
	}, nil, nil)
	if err := metaSvc.RefreshCache(context.Background()); err != nil {
		t.Fatalf("RefreshCache() error = %v", err)
	}
	svc := &FormulaViewService{metadata: metaSvc, validationProjection: newValidationProjectionCache()}

	// Run several times: map iteration order varies across runs, so this exercises
	// the empty-object-before-rule-object ordering that triggered the bug.
	for i := 0; i < 20; i++ {
		svc.validationProjection.clear()
		if err := svc.RecomputeAllValidationProjections(context.Background()); err != nil {
			t.Fatalf("RecomputeAllValidationProjections() error = %v", err)
		}
		fields, ok := svc.validationProjection.get(withRule)
		if !ok {
			t.Fatalf("iter %d: rule-bearing object %s was not rehydrated; an earlier empty object suppressed it", i, withRule)
		}
		if len(fields) != 1 || fields[0] != "FormulaA__c" {
			t.Fatalf("iter %d: %s closure = %v, want [FormulaA__c]", i, withRule, fields)
		}
	}
}

// newComputeOnMissService builds a FormulaViewService backed by a real
// MetadataService cache (schema + rules) with an EMPTY projection cache, i.e. the
// cold-cache state right after a refresh-cache InvalidateAllValidationProjections.
func newComputeOnMissService(t *testing.T, schema *models.ObjectMetadata, rules []*models.ValidationRule) *FormulaViewService {
	t.Helper()
	metaSvc := metadata.NewMetadataService(&projectionRehydrationRepo{
		schemas: []*models.ObjectMetadata{schema},
		rules:   rules,
	}, nil, nil)
	if err := metaSvc.RefreshCache(context.Background()); err != nil {
		t.Fatalf("RefreshCache() error = %v", err)
	}
	return &FormulaViewService{
		metadata:             metaSvc,
		validationProjection: newValidationProjectionCache(),
	}
}

// TestValidationProjectionFieldsOrCompute_ColdMissComputesNarrowSet is the core
// treat-the-cause assertion for #2347: on a cold-cache miss the provider must
// compute and return the NARROW closure (only the computed fields validation
// references, transitively), never report absent and force the runtime into the
// full-set query-backed projection storm. It also pins that the computed closure
// is cached so the next call is a pure lookup.
func TestValidationProjectionFieldsOrCompute_ColdMissComputesNarrowSet(t *testing.T) {
	mode := "active"
	rt := models.FieldType(constants.FieldTypeString)

	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{APIName: "Name", Type: constants.FieldTypeString},
			{APIName: "FormulaReferenced__c", Type: constants.FieldTypeFormula, Formula: strPtr("Name"), FormulaMode: &mode, ReturnType: &rt},
			{APIName: "FormulaUnreferencedA__c", Type: constants.FieldTypeFormula, Formula: strPtr("Id"), FormulaMode: &mode, ReturnType: &rt},
			{APIName: "FormulaUnreferencedB__c", Type: constants.FieldTypeFormula, Formula: strPtr("Id"), FormulaMode: &mode, ReturnType: &rt},
		},
	}
	rules := []*models.ValidationRule{
		{ObjectAPIName: "Account", Active: true, Condition: "FormulaReferenced__c != null"},
	}

	svc := newComputeOnMissService(t, schema, rules)

	// Cold cache: nothing precomputed.
	if _, ok := svc.validationProjection.get("Account"); ok {
		t.Fatal("precondition: projection cache must start cold")
	}

	fields, ok := svc.ValidationProjectionFieldsOrCompute(context.Background(), "Account")
	if !ok {
		t.Fatal("compute-on-miss must answer for an object with active computed fields, not report absent (which forces full-set fallback)")
	}
	if len(fields) != 1 || fields[0] != "FormulaReferenced__c" {
		t.Fatalf("expected narrow closure [FormulaReferenced__c], got %v (full-set regression evaluates all 3 formula fields)", fields)
	}

	// The computed closure must now be cached: next call is a pure lookup.
	cached, ok := svc.validationProjection.get("Account")
	if !ok {
		t.Fatal("compute-on-miss must store the closure so steady state is a map lookup, not a recompute per request")
	}
	if len(cached) != 1 || cached[0] != "FormulaReferenced__c" {
		t.Fatalf("cached closure = %v, want [FormulaReferenced__c]", cached)
	}
}

// TestValidationProjectionFieldsOrCompute_EmptyClosureIsAuthoritative pins the
// subtle case behind the regression: an object whose rules reference only PLAIN
// fields has a genuinely empty narrow closure (project nothing). That empty answer
// must be authoritative and cached — returning ok=true with zero fields — so the
// runtime projects nothing instead of falling back to evaluating every active
// computed field, and so it is not recomputed on every subsequent request.
func TestValidationProjectionFieldsOrCompute_EmptyClosureIsAuthoritative(t *testing.T) {
	mode := "active"
	rt := models.FieldType(constants.FieldTypeString)

	schema := &models.ObjectMetadata{
		APIName: "Lead",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{APIName: "Region__c", Type: constants.FieldTypeString},
			// Active computed fields exist on the object but are NOT referenced
			// by any validation rule (rules touch only the plain Region__c).
			{APIName: "FormulaA__c", Type: constants.FieldTypeFormula, Formula: strPtr("Id"), FormulaMode: &mode, ReturnType: &rt},
			{APIName: "FormulaB__c", Type: constants.FieldTypeFormula, Formula: strPtr("Id"), FormulaMode: &mode, ReturnType: &rt},
		},
	}
	rules := []*models.ValidationRule{
		{ObjectAPIName: "Lead", Active: true, Condition: "Region__c != null"},
	}

	svc := newComputeOnMissService(t, schema, rules)

	fields, ok := svc.ValidationProjectionFieldsOrCompute(context.Background(), "Lead")
	if !ok {
		t.Fatal("empty closure must be answered authoritatively (ok=true, zero fields), not reported absent")
	}
	if len(fields) != 0 {
		t.Fatalf("expected empty narrow closure (rules touch only plain fields), got %v", fields)
	}

	// The empty closure must be cached as present-but-empty so the next call is a
	// lookup, not a recompute that re-parses every formula def.
	cached, ok := svc.validationProjection.get("Lead")
	if !ok {
		t.Fatal("empty closure must be cached as present-but-empty; otherwise every request recomputes and re-parses all formula defs")
	}
	if len(cached) != 0 {
		t.Fatalf("cached empty closure = %v, want []", cached)
	}
}

// TestValidationProjectionFieldsOrCompute_NoActiveComputedFieldsReportsAbsent
// pins that objects with NO active computed fields report absent (ok=false): there
// is nothing to project, and the caller's full-set fallback is also empty, so the
// two are equivalent and no cache entry is needed.
func TestValidationProjectionFieldsOrCompute_NoActiveComputedFieldsReportsAbsent(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Contact",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{APIName: "Name", Type: constants.FieldTypeString},
		},
	}
	rules := []*models.ValidationRule{
		{ObjectAPIName: "Contact", Active: true, Condition: "Name != null"},
	}

	svc := newComputeOnMissService(t, schema, rules)

	fields, ok := svc.ValidationProjectionFieldsOrCompute(context.Background(), "Contact")
	if ok {
		t.Fatalf("object with no active computed fields should report absent, got ok=true fields=%v", fields)
	}
}

// TestValidationProjectionFieldsOrCompute_StaleEpochDropsStoreButAnswers proves
// compute-on-miss preserves the generation-guard contract: if an invalidation
// lands between the epoch capture and the store, the store is dropped (cache stays
// cold for the next caller to recompute against fresh state) but THIS request is
// still answered with the freshly-computed narrow closure. A false store here would
// be the hard_red_line correctness flip the guard exists to prevent.
func TestValidationProjectionFieldsOrCompute_StaleEpochDropsStoreButAnswers(t *testing.T) {
	mode := "active"
	rt := models.FieldType(constants.FieldTypeString)

	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{APIName: "Name", Type: constants.FieldTypeString},
			{APIName: "FormulaReferenced__c", Type: constants.FieldTypeFormula, Formula: strPtr("Name"), FormulaMode: &mode, ReturnType: &rt},
		},
	}
	rules := []*models.ValidationRule{
		{ObjectAPIName: "Account", Active: true, Condition: "FormulaReferenced__c != null"},
	}

	svc := newComputeOnMissService(t, schema, rules)

	// computeValidationProjectionClosureResult stores via setIfGeneration(gen).
	// Drive the same race the guard protects: capture the pre-bump generation, bump
	// it (an invalidation), then prove a stale-gen store is dropped while the
	// computed result is still valid to return for the in-flight request.
	staleGen := svc.validationProjection.currentGeneration()
	svc.validationProjection.clear() // invalidation bumps the generation
	result := svc.computeValidationProjectionClosureResult(schema, mustBuildDefs(t, svc, schema), rules, staleGen)
	if len(result) != 1 || result[0] != "FormulaReferenced__c" {
		t.Fatalf("stale-epoch compute must still RETURN the narrow closure for the in-flight request, got %v", result)
	}
	if _, ok := svc.validationProjection.get("Account"); ok {
		t.Fatal("stale-epoch store must be dropped by the generation guard; a false store is a correctness flip")
	}
}

func TestValidationProjectionFieldsOrCompute_RequestBoundRevisionsNeverShareProcessCache(t *testing.T) {
	first := runtimeValidationProjectionView(t, 1, "FormulaA__c")
	second := runtimeValidationProjectionView(t, 2, "FormulaB__c")
	metadataService, err := metadata.NewRuntimeMetadataService(metadata.RuntimeMetadataViewFromContext)
	if err != nil {
		t.Fatal(err)
	}
	svc := &FormulaViewService{metadata: metadataService, validationProjection: newValidationProjectionCache()}

	firstFields, ok := svc.ValidationProjectionFieldsOrCompute(
		metadata.WithRuntimeMetadataView(context.Background(), first), "Account",
	)
	if !ok || len(firstFields) != 1 || firstFields[0] != "FormulaA__c" {
		t.Fatalf("first Runtime revision projection = %v, %v", firstFields, ok)
	}
	secondFields, ok := svc.ValidationProjectionFieldsOrCompute(
		metadata.WithRuntimeMetadataView(context.Background(), second), "Account",
	)
	if !ok || len(secondFields) != 1 || secondFields[0] != "FormulaB__c" {
		t.Fatalf("second Runtime revision projection = %v, %v", secondFields, ok)
	}
	if _, cached := svc.validationProjection.get("Account"); cached {
		t.Fatal("request-bound Runtime projection escaped into the process-global object cache")
	}
	if fields, cached := svc.ValidationProjectionFields("Account"); cached || fields != nil {
		t.Fatalf("context-free lookup exposed a request-bound projection: %v, %v", fields, cached)
	}
}

func runtimeValidationProjectionView(t *testing.T, revision catalogsnapshot.CatalogRevision, referencedField string) *catalogsnapshot.RuntimeMetadataView {
	t.Helper()
	mode := "active"
	returnType := models.FieldType(constants.FieldTypeString)
	input := catalogsnapshot.CompileInput{
		OrgID: "org-validation-projection",
		Lineage: catalogsnapshot.Lineage{
			CatalogRevision: revision, SchemaGeneration: 1, EngineGeneration: 1,
		},
		Objects: []catalogsnapshot.ObjectInput{{CatalogRevision: revision, Metadata: &models.ObjectMetadata{
			ID: "object-account", APIName: "Account", SharingModel: constants.SharingModelPublicReadWrite,
			Fields: []models.FieldMetadata{
				{ID: "field-id", APIName: constants.FieldID, PhysicalName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
				{ID: "field-name", APIName: constants.FieldName, PhysicalName: constants.FieldName, Type: constants.FieldTypeString},
				{ID: "field-formula-a", APIName: "FormulaA__c", PhysicalName: "FormulaA__c", Type: constants.FieldTypeFormula, Formula: strPtr("Name"), FormulaMode: &mode, ReturnType: &returnType},
				{ID: "field-formula-b", APIName: "FormulaB__c", PhysicalName: "FormulaB__c", Type: constants.FieldTypeFormula, Formula: strPtr("Name"), FormulaMode: &mode, ReturnType: &returnType},
			},
		}}},
		MutationDefinitions: []catalogsnapshot.MutationDefinitionInput{{CatalogRevision: revision, ValidationRule: &models.ValidationRule{
			ID: "validation-account", ObjectAPIName: "Account", Name: "RevisionBoundRule", Active: true,
			Condition: referencedField + " != null", ErrorMessage: "invalid",
		}}},
		Projections: []catalogsnapshot.RuntimeProjectionInput{{
			CatalogRevision: revision, AppInstallationID: "installation-validation-projection",
			CapabilityID: "runtime.records.mutate.v1", Profile: catalogsnapshot.ProjectionRecordMutationV1,
			ObjectAPINames: []string{"Account"},
		}},
	}
	artifact, err := catalogsnapshot.CompileCatalogSnapshot(input)
	if err != nil {
		t.Fatal(err)
	}
	snapshot, err := catalogsnapshot.OpenCatalogSnapshot(artifact)
	if err != nil {
		t.Fatal(err)
	}
	viewArtifact, err := catalogsnapshot.ProjectRuntimeMetadataView(snapshot, catalogsnapshot.ProjectionKey{
		AppInstallationID: "installation-validation-projection", CapabilityID: "runtime.records.mutate.v1",
	})
	if err != nil {
		t.Fatal(err)
	}
	view, err := catalogsnapshot.OpenRuntimeMetadataView(viewArtifact)
	if err != nil {
		t.Fatal(err)
	}
	return view
}

func mustBuildDefs(t *testing.T, svc *FormulaViewService, schema *models.ObjectMetadata) []FormulaDefinition {
	t.Helper()
	defs, err := svc.buildFormulaDefs(context.Background(), schema.APIName, schema, formulastorage.ActiveComputedFields(schema))
	if err != nil {
		t.Fatalf("buildFormulaDefs() error = %v", err)
	}
	return defs
}
