package packs

import (
	"errors"
	"strings"
	"testing"

	"github.com/nexuscrm/shared/pkg/models"
)

// --- buildFlowObjectFieldGraph tests ---

func TestBuildFlowObjectFieldGraph_SingleSpec(t *testing.T) {
	specs := []ObjectSpec{
		{
			APIName:   "Contact",
			TableType: "standard_object",
			Fields: []FieldSpec{
				{APIName: "FirstName"},
				{APIName: "LastName"},
			},
		},
	}
	graph := buildFlowObjectFieldGraph(specs)

	obj := graph["contact"]
	if obj == nil {
		t.Fatal("expected 'contact' in graph")
	}
	for _, f := range []string{"firstname", "lastname", "id", "ownerid", "createddate", "isdeleted"} {
		if _, ok := obj[f]; !ok {
			t.Errorf("expected field %q in contact graph", f)
		}
	}
	// RecordTypeId not added for standard_object
	if _, ok := obj["recordtypeid"]; ok {
		t.Error("RecordTypeId should not be in graph for standard_object")
	}
}

func TestBuildFlowObjectFieldGraph_CustomObjectHasRecordTypeId(t *testing.T) {
	specs := []ObjectSpec{
		{
			APIName:   "MyObj__c",
			TableType: "custom_object",
			Fields:    []FieldSpec{{APIName: "Status__c"}},
		},
	}
	graph := buildFlowObjectFieldGraph(specs)
	obj := graph["myobj__c"]
	if _, ok := obj["recordtypeid"]; !ok {
		t.Error("expected RecordTypeId in graph for custom_object")
	}
}

func TestBuildFlowObjectFieldGraph_UnionMultipleSpecs(t *testing.T) {
	specs := []ObjectSpec{
		{
			APIName:   "Contact",
			TableType: "standard_object",
			Fields:    []FieldSpec{{APIName: "FieldA__c"}},
		},
		{
			APIName:   "Contact",
			TableType: "standard_object",
			Fields:    []FieldSpec{{APIName: "FieldB__c"}},
		},
	}
	graph := buildFlowObjectFieldGraph(specs)
	obj := graph["contact"]
	if _, ok := obj["fielda__c"]; !ok {
		t.Error("FieldA__c should be in union graph")
	}
	if _, ok := obj["fieldb__c"]; !ok {
		t.Error("FieldB__c should be in union graph (union, not overwrite)")
	}
}

func TestBuildFlowObjectFieldGraph_CaseInsensitiveKeys(t *testing.T) {
	specs := []ObjectSpec{
		{
			APIName:   "Account",
			TableType: "standard_object",
			Fields:    []FieldSpec{{APIName: "BillingCity"}},
		},
	}
	graph := buildFlowObjectFieldGraph(specs)
	if _, ok := graph["account"]; !ok {
		t.Error("graph key should be lowercase")
	}
	if _, ok := graph["account"]["billingcity"]; !ok {
		t.Error("field key should be lowercase")
	}
}

func TestBuildFlowObjectFieldGraph_PolymorphicDiscriminatorIncluded(t *testing.T) {
	specs := []ObjectSpec{
		{
			APIName:   "Task",
			TableType: "standard_object",
			Fields: []FieldSpec{
				// Multi-target polymorphic reference → WhatId_type companion must be added.
				{APIName: "WhatId", Type: "reference", ReferenceTo: []string{"Account", "Lead"}},
				// IsPolymorphic flag path.
				{APIName: "WhoId", Type: "reference", ReferenceTo: []string{"Contact"}, IsPolymorphic: true},
				// Non-polymorphic reference: no companion.
				{APIName: "OwnerId", Type: "reference", ReferenceTo: []string{"User"}},
			},
		},
	}
	graph := buildFlowObjectFieldGraph(specs)
	obj := graph["task"]
	if obj == nil {
		t.Fatal("expected 'task' in graph")
	}
	if _, ok := obj["whatid_type"]; !ok {
		t.Error("expected WhatId_type (polymorphic discriminator companion) in graph")
	}
	if _, ok := obj["whoid_type"]; !ok {
		t.Error("expected WhoId_type (IsPolymorphic=true discriminator companion) in graph")
	}
	if _, ok := obj["ownerid_type"]; ok {
		t.Error("OwnerId_type should not be in graph (single-target non-polymorphic reference)")
	}
}

// --- validateFlowFieldReferences tests ---

func TestValidateFlowFieldReferences_ValidFlow(t *testing.T) {
	specs := []ObjectSpec{
		{
			APIName:   "Contact",
			TableType: "standard_object",
			Fields: []FieldSpec{
				{APIName: "Email"},
				{APIName: "Phone"},
			},
		},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		RecordCreates: []models.FlowRecordCreate{
			{
				Name:   "CreateContact",
				Object: "Contact",
				InputAssignments: []models.FlowInputAssignment{
					{Field: "Email"},
					{Field: "Phone"},
				},
			},
		},
	}
	if err := validateFlowFieldReferences("MyFlow.flow.json", meta, graph); err != nil {
		t.Errorf("valid flow should not return error, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_MissingFieldFails(t *testing.T) {
	specs := []ObjectSpec{
		{
			APIName:   "Contact",
			TableType: "standard_object",
			Fields:    []FieldSpec{{APIName: "Email"}},
		},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		RecordCreates: []models.FlowRecordCreate{
			{
				Name:   "CreateContact",
				Object: "Contact",
				InputAssignments: []models.FlowInputAssignment{
					{Field: "TypoField"},
				},
			},
		},
	}
	err := validateFlowFieldReferences("BadFlow.flow.json", meta, graph)
	if err == nil {
		t.Fatal("expected error for missing field")
	}
	if !errors.Is(err, ErrFlowFieldReferenceMissing) {
		t.Errorf("error should wrap ErrFlowFieldReferenceMissing, got: %v", err)
	}
	if !strings.Contains(err.Error(), "TypoField") {
		t.Errorf("error should mention missing field, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_UnknownObjectSkipped(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Contact", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Email"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		RecordCreates: []models.FlowRecordCreate{
			{
				Name:   "CreateDepPackObj",
				Object: "DepPackObject__c",
				InputAssignments: []models.FlowInputAssignment{
					{Field: "AnyField"},
				},
			},
		},
	}
	if err := validateFlowFieldReferences("DepPackFlow.flow.json", meta, graph); err != nil {
		t.Errorf("unknown object should be silently skipped, got: %v", err)
	}
}

// TestValidateFlowFieldReferences_CustomFieldOnKnownObjectSkipped verifies that a
// __c field NOT present in the spec is skipped, not rejected. Custom fields are
// out of scope for construction-time validation; cross-pack __c declarations
// cannot be resolved without peer-pack specs (see issue #1018).
func TestValidateFlowFieldReferences_CustomFieldOnKnownObjectSkipped(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Contact", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Email"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		RecordCreates: []models.FlowRecordCreate{
			{
				Name:   "CreateContact",
				Object: "Contact",
				InputAssignments: []models.FlowInputAssignment{
					{Field: "Email"},
					{Field: "CustomField__c"}, // not in spec but __c → out of scope
				},
			},
		},
	}
	if err := validateFlowFieldReferences("CustomField.flow.json", meta, graph); err != nil {
		t.Errorf("__c field not in spec should be skipped (out of scope): %v", err)
	}
}

// TestValidateFlowFieldReferences_CustomFieldInSpecPasses verifies that a __c field
// that IS declared in the ObjectSpec passes validation (it was unioned into the graph
// by buildFlowObjectFieldGraph).
func TestValidateFlowFieldReferences_CustomFieldInSpecPasses(t *testing.T) {
	specs := []ObjectSpec{
		{
			APIName:   "Contact",
			TableType: "standard_object",
			Fields:    []FieldSpec{{APIName: "Email"}, {APIName: "Priority__c"}},
		},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		RecordCreates: []models.FlowRecordCreate{
			{
				Name:   "CreateContact",
				Object: "Contact",
				InputAssignments: []models.FlowInputAssignment{
					{Field: "Priority__c"},
				},
			},
		},
	}
	if err := validateFlowFieldReferences("CustomInSpec.flow.json", meta, graph); err != nil {
		t.Errorf("__c field declared in spec should pass: %v", err)
	}
}

// TestValidateFlowFieldReferences_StandardFieldTypoFails verifies that a non-__c
// field absent from a known object's spec causes a construction-time failure.
func TestValidateFlowFieldReferences_StandardFieldTypoFails(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Contact", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Email"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		RecordCreates: []models.FlowRecordCreate{
			{
				Name:   "CreateContact",
				Object: "Contact",
				InputAssignments: []models.FlowInputAssignment{
					{Field: "TypoStandardField"}, // non-__c, not in spec → fail closed
				},
			},
		},
	}
	err := validateFlowFieldReferences("TypoFlow.flow.json", meta, graph)
	if err == nil {
		t.Fatal("expected error for non-__c standard field typo on known object")
	}
	if !errors.Is(err, ErrFlowFieldReferenceMissing) {
		t.Errorf("error should wrap ErrFlowFieldReferenceMissing, got: %v", err)
	}
	if !strings.Contains(err.Error(), "TypoStandardField") {
		t.Errorf("error should mention missing field name, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_PlatformSystemFieldsAlwaysValid(t *testing.T) {
	specs := []ObjectSpec{
		{
			APIName:   "Account",
			TableType: "standard_object",
			Fields:    []FieldSpec{},
		},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		RecordCreates: []models.FlowRecordCreate{
			{
				Name:   "CreateAccount",
				Object: "Account",
				InputAssignments: []models.FlowInputAssignment{
					{Field: "Id"},
					{Field: "OwnerId"},
					{Field: "CreatedDate"},
					{Field: "IsDeleted"},
					{Field: "SystemModstamp"},
				},
			},
		},
	}
	if err := validateFlowFieldReferences("SysFields.flow.json", meta, graph); err != nil {
		t.Errorf("platform system fields should always be valid, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_RecordUpdateObjectForm(t *testing.T) {
	specs := []ObjectSpec{
		{
			APIName:   "Lead",
			TableType: "standard_object",
			Fields:    []FieldSpec{{APIName: "Status"}},
		},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		RecordUpdates: []models.FlowRecordUpdate{
			{
				Name:   "UpdateLead",
				Object: "Lead",
				InputAssignments: []models.FlowInputAssignment{
					{Field: "Status"},
				},
			},
		},
	}
	if err := validateFlowFieldReferences("UpdateLead.flow.json", meta, graph); err != nil {
		t.Errorf("valid RecordUpdate should not error, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_RecordUpdateVariableRefSkipped(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Lead", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Status"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		RecordUpdates: []models.FlowRecordUpdate{
			{
				Name:           "UpdateViaRef",
				InputReference: "myLeadVar",
				InputAssignments: []models.FlowInputAssignment{
					{Field: "TypoField"},
				},
			},
		},
	}
	if err := validateFlowFieldReferences("UpdateRef.flow.json", meta, graph); err != nil {
		t.Errorf("RecordUpdate with InputReference should be skipped, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_RecordUpdateMissingFieldFails(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Lead", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Status"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		RecordUpdates: []models.FlowRecordUpdate{
			{
				Name:   "UpdateLead",
				Object: "Lead",
				InputAssignments: []models.FlowInputAssignment{
					{Field: "TypoUpdateField"},
				},
			},
		},
	}
	err := validateFlowFieldReferences("UpdateBad.flow.json", meta, graph)
	if err == nil {
		t.Fatal("expected error for missing field in RecordUpdate")
	}
	if !errors.Is(err, ErrFlowFieldReferenceMissing) {
		t.Errorf("error should wrap ErrFlowFieldReferenceMissing, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_RecordUpdateFilterFieldChecked(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Lead", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Status"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		RecordUpdates: []models.FlowRecordUpdate{
			{
				Name:   "UpdateLead",
				Object: "Lead",
				Filters: []models.FlowRecordFilter{
					{Field: "TypoFilterField", Operator: "EqualTo"},
				},
			},
		},
	}
	err := validateFlowFieldReferences("UpdateFilter.flow.json", meta, graph)
	if err == nil {
		t.Fatal("expected error for missing filter field in RecordUpdate")
	}
	if !errors.Is(err, ErrFlowFieldReferenceMissing) {
		t.Errorf("error should wrap ErrFlowFieldReferenceMissing, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_RecordLookupAllFields(t *testing.T) {
	specs := []ObjectSpec{
		{
			APIName:   "Account",
			TableType: "standard_object",
			Fields:    []FieldSpec{{APIName: "Name"}, {APIName: "BillingCity"}, {APIName: "Rating"}},
		},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		RecordLookups: []models.FlowRecordLookup{
			{
				Name:   "LookupAccount",
				Object: "Account",
				Filters: []models.FlowRecordFilter{
					{Field: "Name", Operator: "EqualTo"},
				},
				OutputAssignments: []models.FlowOutputAssignment{
					{Field: "BillingCity"},
				},
				SortField: "Rating",
			},
		},
	}
	if err := validateFlowFieldReferences("LookupAll.flow.json", meta, graph); err != nil {
		t.Errorf("valid RecordLookup should not error, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_RecordLookupSortFieldMissing(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Account", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Name"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		RecordLookups: []models.FlowRecordLookup{
			{
				Name:      "LookupAccount",
				Object:    "Account",
				SortField: "TypoSortField",
			},
		},
	}
	err := validateFlowFieldReferences("SortField.flow.json", meta, graph)
	if err == nil {
		t.Fatal("expected error for missing sortField")
	}
	if !errors.Is(err, ErrFlowFieldReferenceMissing) {
		t.Errorf("error should wrap ErrFlowFieldReferenceMissing, got: %v", err)
	}
	if !strings.Contains(err.Error(), "TypoSortField") {
		t.Errorf("error should mention missing sortField, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_RecordLookupSortFieldEmpty(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Account", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Name"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		RecordLookups: []models.FlowRecordLookup{
			{
				Name:      "LookupAccount",
				Object:    "Account",
				SortField: "",
			},
		},
	}
	if err := validateFlowFieldReferences("SortEmpty.flow.json", meta, graph); err != nil {
		t.Errorf("empty sortField should be skipped, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_RecordDeleteFilterMissingField(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Lead", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Status"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		RecordDeletes: []models.FlowRecordDelete{
			{
				Name:   "DeleteLead",
				Object: "Lead",
				Filters: []models.FlowRecordFilter{
					{Field: "MissingDeleteField", Operator: "EqualTo"},
				},
			},
		},
	}
	err := validateFlowFieldReferences("DeleteFilter.flow.json", meta, graph)
	if err == nil {
		t.Fatal("expected error for missing recordDelete filter field")
	}
	if !errors.Is(err, ErrFlowFieldReferenceMissing) {
		t.Errorf("error should wrap ErrFlowFieldReferenceMissing, got: %v", err)
	}
	if !strings.Contains(err.Error(), "MissingDeleteField") {
		t.Errorf("error should mention missing field, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_RecordDeleteNoObjectSkipped(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Lead", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Status"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		RecordDeletes: []models.FlowRecordDelete{
			{
				Name:           "DeleteViaVar",
				InputReference: "myLeadVar",
				Filters: []models.FlowRecordFilter{
					{Field: "NoSuchField", Operator: "EqualTo"},
				},
			},
		},
	}
	if err := validateFlowFieldReferences("DeleteNoObj.flow.json", meta, graph); err != nil {
		t.Errorf("recordDelete with no object should be skipped, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_StartFilterMissingField(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Contact", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Email"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		Start: &models.FlowStart{
			Object: "Contact",
			Filters: []models.FlowRecordFilter{
				{Field: "TypoStartField", Operator: "EqualTo"},
			},
		},
	}
	err := validateFlowFieldReferences("StartFilter.flow.json", meta, graph)
	if err == nil {
		t.Fatal("expected error for missing start filter field")
	}
	if !errors.Is(err, ErrFlowFieldReferenceMissing) {
		t.Errorf("error should wrap ErrFlowFieldReferenceMissing, got: %v", err)
	}
	if !strings.Contains(err.Error(), "TypoStartField") {
		t.Errorf("error should mention missing field, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_StartFilterNoObjectSkipped(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Contact", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Email"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		Start: &models.FlowStart{
			Object: "",
			Filters: []models.FlowRecordFilter{
				{Field: "AnyField", Operator: "EqualTo"},
			},
		},
	}
	if err := validateFlowFieldReferences("StartNoObj.flow.json", meta, graph); err != nil {
		t.Errorf("start with no object should be skipped, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_DecisionFieldValid(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Contact", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Email"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		Start: &models.FlowStart{Object: "Contact"},
		Decisions: []models.FlowDecision{
			{
				Name: "CheckEmail",
				Rules: []models.FlowDecisionRule{
					{
						Conditions: []models.FlowCondition{
							{LeftValueReference: "$Record.Email", Operator: "EqualTo"},
						},
					},
				},
			},
		},
	}
	if err := validateFlowFieldReferences("Decision.flow.json", meta, graph); err != nil {
		t.Errorf("valid Decision field ref should not error, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_DecisionFieldMissing(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Contact", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Email"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		Start: &models.FlowStart{Object: "Contact"},
		Decisions: []models.FlowDecision{
			{
				Name: "CheckTypo",
				Rules: []models.FlowDecisionRule{
					{
						Conditions: []models.FlowCondition{
							{LeftValueReference: "$Record.TypoField", Operator: "EqualTo"},
						},
					},
				},
			},
		},
	}
	err := validateFlowFieldReferences("DecisionBad.flow.json", meta, graph)
	if err == nil {
		t.Fatal("expected error for missing Decision field ref")
	}
	if !errors.Is(err, ErrFlowFieldReferenceMissing) {
		t.Errorf("error should wrap ErrFlowFieldReferenceMissing, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_DecisionRecordOldRef(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Contact", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Email"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		Start: &models.FlowStart{Object: "Contact"},
		Decisions: []models.FlowDecision{
			{
				Name: "CheckOld",
				Rules: []models.FlowDecisionRule{
					{
						Conditions: []models.FlowCondition{
							{LeftValueReference: "$Record__Old.Email", Operator: "EqualTo"},
						},
					},
				},
			},
		},
	}
	if err := validateFlowFieldReferences("DecisionOld.flow.json", meta, graph); err != nil {
		t.Errorf("valid $Record__Old field ref should not error, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_DecisionMultiSegmentSkipped(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Contact", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Email"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		Start: &models.FlowStart{Object: "Contact"},
		Decisions: []models.FlowDecision{
			{
				Name: "MultiSeg",
				Rules: []models.FlowDecisionRule{
					{
						Conditions: []models.FlowCondition{
							{LeftValueReference: "$Record.Contact.TypoField", Operator: "EqualTo"},
						},
					},
				},
			},
		},
	}
	if err := validateFlowFieldReferences("DecisionMulti.flow.json", meta, graph); err != nil {
		t.Errorf("multi-segment Decision ref should be skipped, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_DecisionNonRecordRootSkipped(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Contact", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Email"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		Start: &models.FlowStart{Object: "Contact"},
		Decisions: []models.FlowDecision{
			{
				Name: "NonRecord",
				Rules: []models.FlowDecisionRule{
					{
						Conditions: []models.FlowCondition{
							{LeftValueReference: "$User.Something", Operator: "EqualTo"},
						},
					},
				},
			},
		},
	}
	if err := validateFlowFieldReferences("DecisionNonRecord.flow.json", meta, graph); err != nil {
		t.Errorf("non-$Record Decision ref should be skipped, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_DecisionNoTriggerObjectSkipped(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Contact", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Email"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		// No Start set → trigger object unknown
		Decisions: []models.FlowDecision{
			{
				Name: "NoTrigger",
				Rules: []models.FlowDecisionRule{
					{
						Conditions: []models.FlowCondition{
							{LeftValueReference: "$Record.TypoField"},
						},
					},
				},
			},
		},
	}
	if err := validateFlowFieldReferences("NoTrigger.flow.json", meta, graph); err != nil {
		t.Errorf("decision with no trigger object should be skipped, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_WaitEventConditionMissingField(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Contact", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Email"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		Start: &models.FlowStart{Object: "Contact"},
		Waits: []models.FlowWait{
			{
				Name: "WaitForUpdate",
				WaitEvents: []models.FlowWaitEvent{
					{
						Name: "evt1",
						Conditions: []models.FlowCondition{
							{LeftValueReference: "$Record.TypoField", Operator: "EqualTo"},
						},
					},
				},
			},
		},
	}
	err := validateFlowFieldReferences("WaitCond.flow.json", meta, graph)
	if err == nil {
		t.Fatal("expected error for missing wait event condition field")
	}
	if !errors.Is(err, ErrFlowFieldReferenceMissing) {
		t.Errorf("error should wrap ErrFlowFieldReferenceMissing, got: %v", err)
	}
	if !strings.Contains(err.Error(), "TypoField") {
		t.Errorf("error should mention missing field, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_WaitEventConditionNoTriggerObjectSkipped(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Contact", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Email"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		// No Start → trigger object unknown
		Waits: []models.FlowWait{
			{
				Name: "WaitNoTrigger",
				WaitEvents: []models.FlowWaitEvent{
					{
						Name: "evt1",
						Conditions: []models.FlowCondition{
							{LeftValueReference: "$Record.TypoField", Operator: "EqualTo"},
						},
					},
				},
			},
		},
	}
	if err := validateFlowFieldReferences("WaitNoTrigger.flow.json", meta, graph); err != nil {
		t.Errorf("wait with no trigger object should be skipped, got: %v", err)
	}
}

func TestValidateFlowFieldReferences_WaitEventConditionNonRecordRootSkipped(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Contact", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Email"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		Start: &models.FlowStart{Object: "Contact"},
		Waits: []models.FlowWait{
			{
				Name: "WaitUserRef",
				WaitEvents: []models.FlowWaitEvent{
					{
						Name: "evt1",
						Conditions: []models.FlowCondition{
							{LeftValueReference: "$User.Something", Operator: "EqualTo"},
						},
					},
				},
			},
		},
	}
	if err := validateFlowFieldReferences("WaitNonRecord.flow.json", meta, graph); err != nil {
		t.Errorf("non-$Record wait condition should be skipped, got: %v", err)
	}
}

// TestValidateFlowFieldReferences_StartFilterCustomFieldSkipped verifies that a
// __c field in start.filters on a known object is out of scope and not rejected.
func TestValidateFlowFieldReferences_StartFilterCustomFieldSkipped(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Issue", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Subject"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		Start: &models.FlowStart{
			Object: "Issue",
			Filters: []models.FlowRecordFilter{
				{Field: "Subject"},
				{Field: "PeerFilter__c"}, // __c field not in spec → out of scope, skip
			},
		},
	}
	if err := validateFlowFieldReferences("Issue_Trigger.flow.json", meta, graph); err != nil {
		t.Errorf("__c field in start.filters should be out of scope (not rejected): %v", err)
	}
}

// TestValidateFlowFieldReferences_RecordDeleteFilterCustomFieldSkipped verifies that
// a __c field in recordDeletes[].filters is out of scope and not rejected.
func TestValidateFlowFieldReferences_RecordDeleteFilterCustomFieldSkipped(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Issue", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Subject"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		RecordDeletes: []models.FlowRecordDelete{
			{
				Name:   "DeleteIssues",
				Object: "Issue",
				Filters: []models.FlowRecordFilter{
					{Field: "Subject"},
					{Field: "PeerCriteria__c"}, // __c → out of scope
				},
			},
		},
	}
	if err := validateFlowFieldReferences("Issue_Delete.flow.json", meta, graph); err != nil {
		t.Errorf("__c field in recordDeletes.filters should be out of scope: %v", err)
	}
}

// TestValidateFlowFieldReferences_RecordLookupSortFieldCustomFieldSkipped verifies that
// a __c field used as sortField is out of scope and not rejected.
func TestValidateFlowFieldReferences_RecordLookupSortFieldCustomFieldSkipped(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Issue", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Subject"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		RecordLookups: []models.FlowRecordLookup{
			{
				Name:      "LookupIssue",
				Object:    "Issue",
				SortField: "PeerSort__c", // __c → out of scope
			},
		},
	}
	if err := validateFlowFieldReferences("Issue_Lookup.flow.json", meta, graph); err != nil {
		t.Errorf("__c field as sortField should be out of scope: %v", err)
	}
}

// TestValidateFlowFieldReferences_WaitEventConditionCustomFieldSkipped verifies that
// a "$Record.<Field__c>" reference in wait event conditions is out of scope and not rejected.
func TestValidateFlowFieldReferences_WaitEventConditionCustomFieldSkipped(t *testing.T) {
	specs := []ObjectSpec{
		{APIName: "Issue", TableType: "standard_object", Fields: []FieldSpec{{APIName: "Subject"}}},
	}
	graph := buildFlowObjectFieldGraph(specs)

	meta := models.FlowMetadata{
		Start: &models.FlowStart{Object: "Issue"},
		Waits: []models.FlowWait{
			{
				Name: "WaitForCustomField",
				WaitEvents: []models.FlowWaitEvent{
					{
						Name: "evt1",
						Conditions: []models.FlowCondition{
							{LeftValueReference: "$Record.PeerWait__c", Operator: "EqualTo"}, // __c → out of scope
						},
					},
				},
			},
		},
	}
	if err := validateFlowFieldReferences("Issue_Wait.flow.json", meta, graph); err != nil {
		t.Errorf("__c field in wait event condition should be out of scope: %v", err)
	}
}

// --- GAP 1 (pack-apply path): platform fields must be accepted by validateFlowFilterFormulaSyntax ---

// TestValidateFlowFilterFormulaSyntax_PlatformFieldsAccepted verifies that
// platform-owned fields (Id, OwnerId, CreatedDate, RecordTypeId for custom
// objects) are ACCEPTED in filterFormula Tier-3 validation even though they
// are absent from ObjectSpec.Fields.
// This is GAP 1 from issue #2047 / PR #1736: BuildObjectMetadataIndex must
// augment each ObjectMetadata with platform fields so that pack-apply does
// not false-reject valid $Record.Id / $Record.OwnerId references.
func TestValidateFlowFilterFormulaSyntax_PlatformFieldsAccepted(t *testing.T) {
	// Spec without any explicit platform fields — simulates a typical pack spec
	// that only declares application-domain fields.
	specs := []ObjectSpec{
		{
			APIName:   "Account",
			TableType: "standard_object",
			Fields:    []FieldSpec{{APIName: "Name"}},
		},
	}

	tests := []struct {
		name string
		expr string
	}{
		{"id_not_null", "$Record.Id != null"},
		{"owner_id_eq", "$Record.OwnerId = '005000000000001'"},
		{"created_date_not_null", "$Record.CreatedDate != null"},
		{"created_by_id_not_null", "$Record.CreatedById != null"},
		{"last_modified_date", "$Record.LastModifiedDate != null"},
		{"last_modified_by_id", "$Record.LastModifiedById != null"},
		{"is_deleted", "$Record.IsDeleted = false"},
		{"system_modstamp", "$Record.SystemModstamp != null"},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			meta := models.FlowMetadata{
				Start: &models.FlowStart{
					Object:        "Account",
					FilterFormula: tc.expr,
				},
			}
			if err := validateFlowFilterFormulaSyntax("PlatformFields.flow.json", meta, specs); err != nil {
				t.Errorf("platform field %q should be ACCEPTED but got error: %v", tc.expr, err)
			}
		})
	}
}

// TestValidateFlowFilterFormulaSyntax_PlatformFieldsAccepted_CustomObject verifies
// that RecordTypeId is also accepted for custom objects (GAP 1 — custom object path).
func TestValidateFlowFilterFormulaSyntax_PlatformFieldsAccepted_CustomObject(t *testing.T) {
	specs := []ObjectSpec{
		{
			APIName:   "MyObj__c",
			TableType: "custom_object",
			Fields:    []FieldSpec{{APIName: "Status__c"}},
		},
	}
	meta := models.FlowMetadata{
		Start: &models.FlowStart{
			Object:        "MyObj__c",
			FilterFormula: "$Record.RecordTypeId != null",
		},
	}
	if err := validateFlowFilterFormulaSyntax("CustomObj.flow.json", meta, specs); err != nil {
		t.Errorf("RecordTypeId on custom object should be ACCEPTED but got: %v", err)
	}
}

// TestValidateFlowFilterFormulaSyntax_NonExistentFieldStillRejected ensures that
// GAP 1 fix does not break the rejection of genuinely missing fields.
func TestValidateFlowFilterFormulaSyntax_NonExistentFieldStillRejected(t *testing.T) {
	specs := []ObjectSpec{
		{
			APIName:   "Account",
			TableType: "standard_object",
			Fields:    []FieldSpec{{APIName: "Name"}},
		},
	}
	meta := models.FlowMetadata{
		Start: &models.FlowStart{
			Object:        "Account",
			FilterFormula: "$Record.TypoFieldName != null",
		},
	}
	if err := validateFlowFilterFormulaSyntax("BadField.flow.json", meta, specs); err == nil {
		t.Error("non-existent field should be REJECTED by filterFormula Tier-3 but got nil")
	}
}
