package programchecker

import (
	"context"
	"testing"

	"github.com/nexuscrm/kernel/pkg/formula"
	"github.com/nexuscrm/shared/pkg/constants"
	sharedcontract "github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
	connectorcontract "github.com/pingcap-inc/nexuscrm-contract/pkg/connector"
)

func TestCheckFlowMetadataChecksActionSignaturesAndConnectorMappings(t *testing.T) {
	reader := newFakeProgramMetadataReader(
		objectForFlowCheck("ProgramContact", []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{APIName: "Email", Type: constants.FieldTypeString, Updateable: true},
			{APIName: "Score", Type: constants.FieldTypeDouble},
		}),
	)
	actionReader := ActionSignatureReaderFunc(func(_ context.Context, actionName string) (ActionSignature, bool, error) {
		switch actionName {
		case "ProgramAction":
			return ActionSignature{
				Name: "ProgramAction",
				Inputs: []sharedcontract.StandardActionSchemaField{
					{Name: "recordId", Type: "string", Required: true},
					{Name: "score", Type: "number"},
				},
				Outputs: []sharedcontract.StandardActionSchemaField{
					{Name: "resultId", Type: "string"},
				},
			}, true, nil
		case "applyConnectorFieldMappings":
			return applyConnectorFieldMappingsSignature(), true, nil
		default:
			return ActionSignature{}, false, nil
		}
	})
	var connectorQueries []connectorcontract.ResolveQuery
	connectorReader := ConnectorMappingReaderFunc(func(_ context.Context, q connectorcontract.ResolveQuery) ([]connectorcontract.ConnectorFieldMappingInput, error) {
		connectorQueries = append(connectorQueries, q)
		return []connectorcontract.ConnectorFieldMappingInput{{
			Source:       "program-pack",
			SourceObject: "ProgramContact",
			TargetObject: "ProgramContact",
			ActionType:   "syncContact",
			SourceField:  "Email",
			TargetField:  "Email",
			Direction:    connectorcontract.DirectionInbound,
		}}, nil
	})

	diags := CheckFlowMetadata(context.Background(), models.FlowMetadata{
		Start: &models.FlowStart{Object: "ProgramContact"},
		Variables: []models.FlowVariable{
			{Name: "sourceRecord", DataType: "Object", ObjectType: "ProgramContact"},
			{Name: "scoreVar", DataType: "Number"},
			{Name: "resultIdVar", DataType: "String"},
		},
		RecordCreates: []models.FlowRecordCreate{{
			Name:   "CreateMappedContact",
			Object: "ProgramContact",
			FieldMapping: &models.FlowFieldMappingRef{
				SourceObject:    "ProgramContact",
				SourceReference: "sourceRecord",
				TargetObject:    "ProgramContact",
				ActionType:      "convert",
			},
		}},
		ActionCalls: []models.FlowActionCall{
			{
				Name:       "InvokeAction",
				ActionName: "ProgramAction",
				ActionType: "flow",
				InputParameters: []models.FlowActionParameter{
					{Name: "recordId", Value: map[string]interface{}{"elementReference": "$Record.Id"}},
					{Name: "score", Value: map[string]interface{}{"elementReference": "scoreVar"}},
				},
				OutputParameters: []models.FlowActionParameter{
					{Name: "resultId", Value: "resultIdVar"},
				},
			},
			{
				Name:       "ApplyMappings",
				ActionName: "applyConnectorFieldMappings",
				ActionType: "applyConnectorFieldMappings",
				InputParameters: []models.FlowActionParameter{
					{Name: "source", Value: map[string]interface{}{"stringValue": "program-pack"}},
					{Name: "sourceObject", Value: map[string]interface{}{"stringValue": "ProgramContact"}},
					{Name: "targetObject", Value: map[string]interface{}{"stringValue": "ProgramContact"}},
					{Name: "actionType", Value: map[string]interface{}{"stringValue": "syncContact"}},
					{Name: "direction", Value: map[string]interface{}{"stringValue": "inbound"}},
					{Name: "targetId", Value: map[string]interface{}{"elementReference": "$Record.Id"}},
					{Name: "sourceRecord", Value: map[string]interface{}{"elementReference": "sourceRecord"}},
				},
			},
		},
	}, FlowCheckOptions{
		Metadata:   reader,
		Actions:    actionReader,
		Connectors: connectorReader,
		OwnerPack:  "program-pack",
	})

	if len(diags) != 0 {
		t.Fatalf("CheckFlowMetadata diagnostics = %#v, want none", diags)
	}
	if len(connectorQueries) != 1 {
		t.Fatalf("connector queries = %#v, want one", connectorQueries)
	}
	if got, want := connectorQueries[0].Source, "program-pack"; got != want {
		t.Fatalf("connector query Source = %q, want %q", got, want)
	}
}

func TestCheckFlowMetadataReportsActionAndConnectorDiagnostics(t *testing.T) {
	reader := newFakeProgramMetadataReader(
		objectForFlowCheck("ProgramContact", []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{APIName: "Email", Type: constants.FieldTypeString, Updateable: true},
		}),
	)
	actionReader := ActionSignatureReaderFunc(func(_ context.Context, actionName string) (ActionSignature, bool, error) {
		switch actionName {
		case "BadAction":
			return ActionSignature{
				Name: "BadAction",
				Inputs: []sharedcontract.StandardActionSchemaField{
					{Name: "goodInput", Type: "string", Required: true},
					{Name: "requiredScore", Type: "number", Required: true},
				},
				Outputs: []sharedcontract.StandardActionSchemaField{
					{Name: "resultId", Type: "string"},
				},
			}, true, nil
		case "applyConnectorFieldMappings":
			return applyConnectorFieldMappingsSignature(), true, nil
		default:
			return ActionSignature{}, false, nil
		}
	})
	connectorReader := ConnectorMappingReaderFunc(func(context.Context, connectorcontract.ResolveQuery) ([]connectorcontract.ConnectorFieldMappingInput, error) {
		return nil, nil
	})

	diags := CheckFlowMetadata(context.Background(), models.FlowMetadata{
		Start: &models.FlowStart{Object: "ProgramContact"},
		Variables: []models.FlowVariable{
			{Name: "numberVar", DataType: "Number"},
			{Name: "sourceRecord", DataType: "Object", ObjectType: "ProgramContact"},
		},
		ActionCalls: []models.FlowActionCall{
			{
				Name:       "MissingAction",
				ActionName: "DoesNotExist",
				ActionType: "flow",
			},
			{
				Name:       "BadActionCall",
				ActionName: "BadAction",
				ActionType: "flow",
				InputParameters: []models.FlowActionParameter{
					{Name: "unknown", Value: map[string]interface{}{"stringValue": "x"}},
					{Name: "goodInput", Value: map[string]interface{}{"booleanValue": true}},
				},
				OutputParameters: []models.FlowActionParameter{
					{Name: "unknownOut", Value: "numberVar"},
					{Name: "resultId", Value: "numberVar"},
				},
			},
			{
				Name:       "ApplyMappings",
				ActionName: "applyConnectorFieldMappings",
				ActionType: "applyConnectorFieldMappings",
				InputParameters: []models.FlowActionParameter{
					{Name: "source", Value: map[string]interface{}{"stringValue": "program-pack"}},
					{Name: "sourceObject", Value: map[string]interface{}{"stringValue": "ProgramContact"}},
					{Name: "targetObject", Value: map[string]interface{}{"stringValue": "ProgramContact"}},
					{Name: "actionType", Value: map[string]interface{}{"stringValue": "syncContact"}},
					{Name: "direction", Value: map[string]interface{}{"stringValue": "inbound"}},
					{Name: "targetId", Value: map[string]interface{}{"elementReference": "$Record.Id"}},
					{Name: "sourceRecord", Value: map[string]interface{}{"elementReference": "sourceRecord"}},
				},
			},
		},
	}, FlowCheckOptions{
		Metadata:   reader,
		Actions:    actionReader,
		Connectors: connectorReader,
		OwnerPack:  "program-pack",
	})

	assertDiagnosticCode(t, diags, "metadata_program.missing_action")
	assertDiagnosticCode(t, diags, "metadata_program.unknown_action_input")
	assertDiagnosticCode(t, diags, "metadata_program.missing_action_input")
	assertDiagnosticCode(t, diags, "metadata_program.unknown_action_output")
	assertDiagnosticCode(t, diags, "metadata_program.type_mismatch")
	assertDiagnosticCode(t, diags, "metadata_program.missing_connector_mapping")
}

func TestCheckFlowMetadataUsesActionTypeSignatureFallback(t *testing.T) {
	reader := newFakeProgramMetadataReader(objectForFlowCheck("ProgramContact", []models.FieldMetadata{
		{APIName: "Id", Type: constants.FieldTypeString},
	}))
	actionReader := ActionSignatureReaderFunc(func(_ context.Context, actionName string) (ActionSignature, bool, error) {
		switch actionName {
		case "sendEmail":
			return ActionSignature{
				Name: "sendEmail",
				Inputs: []sharedcontract.StandardActionSchemaField{
					{Name: "templateId", Type: "string", Required: true},
				},
				Outputs: []sharedcontract.StandardActionSchemaField{
					{Name: "messageId", Type: "string"},
				},
			}, true, nil
		case "registerConnectorInboundRemoteAuthorship":
			return ActionSignature{Name: "registerConnectorInboundRemoteAuthorship"}, true, nil
		default:
			return ActionSignature{}, false, nil
		}
	})

	diags := CheckFlowMetadata(context.Background(), models.FlowMetadata{
		Start:     &models.FlowStart{Object: "ProgramContact"},
		Variables: []models.FlowVariable{{Name: "messageIdVar", DataType: "String"}},
		ActionCalls: []models.FlowActionCall{{
			Name:       "SendEmail",
			ActionName: "PackSpecificEmailName",
			ActionType: "sendEmail",
			InputParameters: []models.FlowActionParameter{
				{Name: "templateId", Value: map[string]interface{}{"stringValue": "tmpl-1"}},
			},
			OutputParameters: []models.FlowActionParameter{
				{Name: "messageId", Value: "messageIdVar"},
			},
		}, {
			Name:       "RegisterInbound",
			ActionType: "registerConnectorInboundRemoteAuthorship",
		}},
	}, FlowCheckOptions{Metadata: reader, Actions: actionReader})

	if len(diags) != 0 {
		t.Fatalf("CheckFlowMetadata diagnostics = %#v, want none", diags)
	}
}

func TestCheckFlowMetadataPreloadsActionOutputTypesForFormulas(t *testing.T) {
	reader := newFakeProgramMetadataReader(objectForFlowCheck("ProgramContact", []models.FieldMetadata{
		{APIName: "Id", Type: constants.FieldTypeString},
	}))
	actionReader := ActionSignatureReaderFunc(func(_ context.Context, actionName string) (ActionSignature, bool, error) {
		if actionName != "sendEmail" {
			return ActionSignature{}, false, nil
		}
		return ActionSignature{
			Name: "sendEmail",
			Outputs: []sharedcontract.StandardActionSchemaField{
				{Name: "emailResult", Type: "string"},
			},
		}, true, nil
	})
	formulas, err := formula.ExecutableFunctionRegistryWithBundles(nil)
	if err != nil {
		t.Fatalf("ExecutableFunctionRegistryWithBundles: %v", err)
	}

	diags := CheckFlowMetadata(context.Background(), models.FlowMetadata{
		Start: &models.FlowStart{Object: "ProgramContact"},
		ActionCalls: []models.FlowActionCall{{
			Name:       "SendEmail",
			ActionName: "sendEmail",
			ActionType: "sendEmail",
			OutputParameters: []models.FlowActionParameter{{
				Name:  "emailResult",
				Value: "emailResult",
			}},
		}},
		Formulas: []models.FlowFormula{
			{Name: "ActionOutputText", DataType: "Text", Expression: "emailResult"},
			{Name: "ActionOutputTextAsNumber", DataType: "Number", Expression: "emailResult + 1"},
		},
	}, FlowCheckOptions{
		Metadata: reader,
		Actions:  actionReader,
		Formula:  NewFormulaCatalogReader(formulas),
	})

	if got := countDiagnosticsForReference(diags, "ActionOutputText"); got != 0 {
		t.Fatalf("ActionOutputText diagnostics = %d, want none in %#v", got, diags)
	}
	assertDiagnosticReference(t, diags, "ActionOutputTextAsNumber")
}

func TestCheckFlowMetadataPreloadsConnectorRemoteIdentityOutputTypes(t *testing.T) {
	reader := newFakeProgramMetadataReader(objectForFlowCheck("ProgramContact", []models.FieldMetadata{
		{APIName: "Id", Type: constants.FieldTypeString},
	}))
	actionReader := ActionSignatureReaderFunc(func(_ context.Context, actionName string) (ActionSignature, bool, error) {
		if actionName != "resolveConnectorRemoteIdentity" {
			return ActionSignature{}, false, nil
		}
		return ActionSignature{
			Name: "resolveConnectorRemoteIdentity",
			Outputs: []sharedcontract.StandardActionSchemaField{
				{Name: "sourceRecordId", Type: "string"},
			},
		}, true, nil
	})
	formulas, err := formula.ExecutableFunctionRegistryWithBundles(nil)
	if err != nil {
		t.Fatalf("ExecutableFunctionRegistryWithBundles: %v", err)
	}

	diags := CheckFlowMetadata(context.Background(), models.FlowMetadata{
		Start: &models.FlowStart{Object: "ProgramContact"},
		ActionCalls: []models.FlowActionCall{{
			Name:       "ResolveDurableIdentity",
			ActionName: "resolveConnectorRemoteIdentity",
			ActionType: "resolveConnectorRemoteIdentity",
			OutputParameters: []models.FlowActionParameter{{
				Name: "sourceRecordId",
				Value: map[string]any{
					"elementReference": "durableSourceRecordId",
				},
			}},
		}},
		Formulas: []models.FlowFormula{
			{Name: "DurableOwnerId", DataType: "Text", Expression: "durableSourceRecordId"},
			{Name: "DurableOwnerIdAsNumber", DataType: "Number", Expression: "durableSourceRecordId + 1"},
		},
	}, FlowCheckOptions{
		Metadata: reader,
		Actions:  actionReader,
		Formula:  NewFormulaCatalogReader(formulas),
	})

	if got := countDiagnosticsForReference(diags, "DurableOwnerId"); got != 0 {
		t.Fatalf("DurableOwnerId diagnostics = %d, want none in %#v", got, diags)
	}
	assertDiagnosticReference(t, diags, "DurableOwnerIdAsNumber")
}

func TestCheckFlowMetadataRejectsRuntimeGlobalsForFlowActionInputs(t *testing.T) {
	reader := newFakeProgramMetadataReader(objectForFlowCheck("ProgramContact", []models.FieldMetadata{
		{APIName: "Id", Type: constants.FieldTypeString},
	}))
	actionReader := ActionSignatureReaderFunc(func(_ context.Context, actionName string) (ActionSignature, bool, error) {
		if actionName != "ChildFlow" {
			return ActionSignature{}, false, nil
		}
		return ActionSignature{
			Name: "ChildFlow",
			Inputs: []sharedcontract.StandardActionSchemaField{
				{Name: "userEmail", Type: "string"},
			},
		}, true, nil
	})

	diags := CheckFlowMetadata(context.Background(), models.FlowMetadata{
		Start: &models.FlowStart{Object: "ProgramContact"},
		ActionCalls: []models.FlowActionCall{{
			Name:       "InvokeChild",
			ActionName: "ChildFlow",
			ActionType: "flow",
			InputParameters: []models.FlowActionParameter{{
				Name:  "userEmail",
				Value: map[string]interface{}{"elementReference": "$User.Email"},
			}},
		}},
	}, FlowCheckOptions{
		Metadata: reader,
		Actions:  actionReader,
	})

	assertDiagnosticReference(t, diags, "$User.Email")
}

func TestCheckFlowMetadataPreloadsPreviewRecordUpdateElementReferenceAlias(t *testing.T) {
	reader := newFakeProgramMetadataReader(objectForFlowCheck("ProgramContact", []models.FieldMetadata{
		{APIName: "Id", Type: constants.FieldTypeString},
		{APIName: "Email", Type: constants.FieldTypeString},
	}))
	formulas, err := formula.ExecutableFunctionRegistryWithBundles(nil)
	if err != nil {
		t.Fatalf("ExecutableFunctionRegistryWithBundles: %v", err)
	}

	diags := CheckFlowMetadata(context.Background(), models.FlowMetadata{
		Start: &models.FlowStart{Object: "ProgramContact"},
		ActionCalls: []models.FlowActionCall{{
			Name:       "PreviewContact",
			ActionType: "previewRecordUpdate",
			InputParameters: []models.FlowActionParameter{
				{Name: "object", Value: map[string]interface{}{"stringValue": "ProgramContact"}},
				{Name: "outputReference", Value: map[string]interface{}{"elementReference": "previewContact"}},
			},
		}},
		Formulas: []models.FlowFormula{{
			Name:       "PreviewEmail",
			DataType:   "Text",
			Expression: "previewContact.Email",
		}},
	}, FlowCheckOptions{
		Metadata: reader,
		Formula:  NewFormulaCatalogReader(formulas),
	})

	if len(diags) != 0 {
		t.Fatalf("CheckFlowMetadata diagnostics = %#v, want none", diags)
	}
}

func TestCheckFlowMetadataSkipsParametersForRegisteredActionCallHandler(t *testing.T) {
	reader := newFakeProgramMetadataReader(objectForFlowCheck("ProgramContact", []models.FieldMetadata{
		{APIName: "Id", Type: constants.FieldTypeString},
	}))
	actionReader := ActionSignatureReaderFunc(func(_ context.Context, actionName string) (ActionSignature, bool, error) {
		if actionName != "emailSimple" {
			return ActionSignature{}, false, nil
		}
		return ActionSignature{Name: "emailSimple", SkipParameterValidation: true}, true, nil
	})

	diags := CheckFlowMetadata(context.Background(), models.FlowMetadata{
		Start: &models.FlowStart{Object: "ProgramContact"},
		ActionCalls: []models.FlowActionCall{{
			Name:       "SendLegacyEmail",
			ActionType: "emailSimple",
			InputParameters: []models.FlowActionParameter{
				{Name: "recipient", Value: map[string]interface{}{"elementReference": "$Record.Id"}},
				{Name: "emailTemplate", Value: map[string]interface{}{"stringValue": "welcome"}},
			},
		}},
	}, FlowCheckOptions{Metadata: reader, Actions: actionReader})

	if len(diags) != 0 {
		t.Fatalf("CheckFlowMetadata diagnostics = %#v, want none", diags)
	}
}

func TestCheckFlowMetadataSkipsOptionalConnectorResolution(t *testing.T) {
	reader := newFakeProgramMetadataReader(objectForFlowCheck("ProgramContact", []models.FieldMetadata{
		{APIName: "Id", Type: constants.FieldTypeString},
	}))
	meta := models.FlowMetadata{
		Start: &models.FlowStart{Object: "ProgramContact"},
		ActionCalls: []models.FlowActionCall{{
			Name:       "ApplyMappings",
			ActionName: "applyConnectorFieldMappings",
			ActionType: "applyConnectorFieldMappings",
			InputParameters: []models.FlowActionParameter{
				{Name: "sourceObject", Value: map[string]interface{}{"elementReference": "dynamicSourceObject"}},
				{Name: "targetObject", Value: map[string]interface{}{"stringValue": "ProgramContact"}},
				{Name: "actionType", Value: map[string]interface{}{"stringValue": "syncContact"}},
				{Name: "direction", Value: map[string]interface{}{"stringValue": "inbound"}},
			},
		}},
	}

	withoutReader := CheckFlowMetadata(context.Background(), meta, FlowCheckOptions{Metadata: reader, OwnerPack: "program-pack"})
	if len(withoutReader) != 0 {
		t.Fatalf("without connector reader diagnostics = %#v, want none", withoutReader)
	}
	withReader := CheckFlowMetadata(context.Background(), meta, FlowCheckOptions{
		Metadata:  reader,
		OwnerPack: "program-pack",
		Connectors: ConnectorMappingReaderFunc(func(context.Context, connectorcontract.ResolveQuery) ([]connectorcontract.ConnectorFieldMappingInput, error) {
			return nil, nil
		}),
	})
	if len(withReader) != 0 {
		t.Fatalf("dynamic tuple diagnostics = %#v, want none", withReader)
	}
}

func TestCheckFlowMetadataRejectsFieldMappingScalarRefsAndAcceptsDottedTargets(t *testing.T) {
	reader := newFakeProgramMetadataReader(
		objectForFlowCheck("ProgramChild", []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{APIName: "ParentId", Type: constants.FieldTypeReference, ReferenceTo: []string{"ProgramParent"}},
		}),
		objectForFlowCheck("ProgramParent", []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
			{APIName: "Notes", Type: constants.FieldTypeString, Updateable: true},
		}),
		objectForFlowCheck("ProgramSource", []models.FieldMetadata{
			{APIName: "Id", Type: constants.FieldTypeString},
		}),
	)
	reader.relationships["programchild:parent"] = fakeRelationship{foreignKey: "ParentId", targets: []string{"ProgramParent"}}

	okDiags := CheckFlowMetadata(context.Background(), models.FlowMetadata{
		Start: &models.FlowStart{Object: "ProgramChild"},
		Variables: []models.FlowVariable{
			{Name: "sourceRecord", DataType: "Object", ObjectType: "ProgramSource"},
		},
		RecordUpdates: []models.FlowRecordUpdate{{
			Name:           "UpdateParent",
			Object:         "ProgramParent",
			InputReference: "$Record.Parent",
			FieldMapping: &models.FlowFieldMappingRef{
				SourceObject:    "ProgramSource",
				SourceReference: "sourceRecord",
				TargetObject:    "ProgramParent",
				TargetReference: "$Record.Parent",
				ActionType:      "syncParent",
				MergeMode:       "fillBlankOnly",
			},
		}},
	}, FlowCheckOptions{Metadata: reader})
	if len(okDiags) != 0 {
		t.Fatalf("dotted target diagnostics = %#v, want none", okDiags)
	}

	badDiags := CheckFlowMetadata(context.Background(), models.FlowMetadata{
		Start:     &models.FlowStart{Object: "ProgramChild"},
		Variables: []models.FlowVariable{{Name: "sourceId", DataType: "String"}},
		RecordCreates: []models.FlowRecordCreate{{
			Name:   "CreateFromScalar",
			Object: "ProgramParent",
			FieldMapping: &models.FlowFieldMappingRef{
				SourceObject:    "ProgramSource",
				SourceReference: "sourceId",
				TargetObject:    "ProgramParent",
				ActionType:      "syncParent",
			},
		}},
	}, FlowCheckOptions{Metadata: reader})
	assertDiagnosticCode(t, badDiags, "metadata_program.type_mismatch")
}

func TestCheckFlowMetadataPreservesActionParameterCaseAndRejectsBlankRequiredInputs(t *testing.T) {
	reader := newFakeProgramMetadataReader(objectForFlowCheck("ProgramContact", []models.FieldMetadata{
		{APIName: "Id", Type: constants.FieldTypeString},
	}))
	actionReader := ActionSignatureReaderFunc(func(_ context.Context, actionName string) (ActionSignature, bool, error) {
		return ActionSignature{
			Name: "CaseAction",
			Inputs: []sharedcontract.StandardActionSchemaField{
				{Name: "recordId", Type: "string", Required: true},
			},
		}, true, nil
	})

	diags := CheckFlowMetadata(context.Background(), models.FlowMetadata{
		Start: &models.FlowStart{Object: "ProgramContact"},
		ActionCalls: []models.FlowActionCall{{
			Name:       "CaseActionCall",
			ActionName: "CaseAction",
			ActionType: "flow",
			InputParameters: []models.FlowActionParameter{
				{Name: "RecordId", Value: map[string]interface{}{"stringValue": ""}},
			},
		}},
	}, FlowCheckOptions{Metadata: reader, Actions: actionReader})

	assertDiagnosticCode(t, diags, "metadata_program.unknown_action_input")
	assertDiagnosticCode(t, diags, "metadata_program.missing_action_input")
}

func TestCheckFlowMetadataUsesDeclaredActionCollectionShape(t *testing.T) {
	reader := newFakeProgramMetadataReader(objectForFlowCheck("ProgramContact", []models.FieldMetadata{
		{APIName: "Id", Type: constants.FieldTypeString},
	}))
	actionReader := ActionSignatureReaderFunc(func(_ context.Context, _ string) (ActionSignature, bool, error) {
		return ActionSignature{
			Name: "Send",
			Inputs: []sharedcontract.StandardActionSchemaField{{
				Name: "attachments", Type: "id", IsCollection: true,
			}},
		}, true, nil
	})

	diags := CheckFlowMetadata(context.Background(), models.FlowMetadata{
		Start:     &models.FlowStart{Object: "ProgramContact"},
		Variables: []models.FlowVariable{{Name: "attachmentIds", DataType: "String", IsCollection: true}},
		ActionCalls: []models.FlowActionCall{{
			Name: "Send", ActionName: "Send", ActionType: "flow",
			InputParameters: []models.FlowActionParameter{{
				Name: "attachments", Value: map[string]interface{}{"elementReference": "attachmentIds"},
			}},
		}},
	}, FlowCheckOptions{Metadata: reader, Actions: actionReader})
	for _, diag := range diags {
		if diag.Code == "metadata_program.type_mismatch" {
			t.Fatalf("declared collection input was treated as scalar: %#v", diags)
		}
	}
}

func applyConnectorFieldMappingsSignature() ActionSignature {
	return ActionSignature{
		Name: "applyConnectorFieldMappings",
		Inputs: []sharedcontract.StandardActionSchemaField{
			{Name: "source", Type: "string"},
			{Name: "sourceObject", Type: "string", Required: true},
			{Name: "targetObject", Type: "string", Required: true},
			{Name: "actionType", Type: "string", Required: true},
			{Name: "direction", Type: "string", Required: true},
			{Name: "targetId", Type: "string", Required: true},
			{Name: "sourceRecord", Type: "object", ReferenceTo: "ProgramContact", Required: true},
		},
	}
}
