package packs

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/nexuscrm/records"
	"github.com/nexuscrm/shared/pkg/models"
	cp "github.com/pingcap-inc/nexuscrm-contract/pkg/controlplane"
	seedrows "github.com/pingcap-inc/nexuscrm-contract/pkg/seedrows"
)

type sourceBackedComponentBuildContext struct {
	p                               *SourceBackedPack
	parsedObjectSpecs               map[string][]ObjectSpec
	overlaidObjectSpecs             map[string]map[string]struct{}
	objectSpecIndex                 map[string]sourceBackedObjectSpecTarget
	packObjectSpecs                 []ObjectSpec
	packObjectSchemas               map[string]*models.ObjectMetadata
	standaloneListFieldsObjects     map[string]struct{}
	lockedObjectSpecListFields      map[string]struct{}
	recordTypeIndex                 map[string]sourceBackedRecordTypeTarget
	rtpvBatch                       *recordTypePicklistValuePackBatch
	pageLayoutBatch                 *sourceBackedPageLayoutPackBatch
	layoutAssignmentBatch           *sourceBackedLayoutAssignmentPackBatch
	reportTypeBatch                 *sourceBackedReportTypePackBatch
	emailTemplateBatch              *emailTemplatePackBatch
	webhookSecretBatch              *webhookSecretPackBatch
	connectorRateLimitBatch         *connectorRateLimitPackBatch
	objectRecordMutationPolicyBatch *objectRecordMutationPolicyPackBatch
	objectLifecyclePolicyBatch      *objectLifecyclePolicyPackBatch
	flowActionCallHandlerBatch      *flowActionCallHandlerPackBatch
	connectorCalloutProfileBatch    *connectorCalloutProfilePackBatch
	systemPermissionGrantBatch      *systemPermissionGrantPackBatch
	connectedAppPublicScopePolicy   records.ConnectedAppPublicScopePolicyProvider
	connectedAppDeclaredIDs         map[string]struct{}
	customFieldDeclarations         []declaredCustomFieldExtension
	folderTypeRows                  []sourceBackedFolderTypeRow
	seenChildCloneRules             bool
	seenFolderTypes                 bool

	// externalRoutes diverts selected component kinds away from the internal
	// method-value dispatch and through an external (contract-only) build
	// handler. Empty by default; only kinds present here are diverted, every
	// other kind keeps the internal sourceBuildHandler path untouched.
	externalRoutes map[ComponentKind]externalBuildRoute
	// currentComponent is the engine ManifestComponent for the component being
	// built right now. It is set immediately before invoking an external route
	// so the engine's cp build-context sinks can reconstruct the component from
	// the full engine ManifestComponent (which carries DependsOn etc.), not the
	// lossy public cp.ManifestComponent in the request.
	currentComponent              ManifestComponent
	currentPayload                []byte
	currentExternalRoute          *externalBuildRoute
	currentManagedDescriptorCount int
	currentManagedBuildErr        error
}

type sourceBackedComponentBuildHandler func(*sourceBackedComponentBuildContext, ManifestComponent, []byte) error

func attachSourceBackedComponentBuildHandlers(contracts map[ComponentKind]ComponentKindContract) {
	for kind, handler := range map[ComponentKind]sourceBackedComponentBuildHandler{
		ComponentKindObjectSpec:                    (*sourceBackedComponentBuildContext).buildObjectSpec,
		ComponentKindObjectRecordMutationPolicy:    (*sourceBackedComponentBuildContext).buildObjectRecordMutationPolicy,
		ComponentKindObjectLifecyclePolicy:         (*sourceBackedComponentBuildContext).buildObjectLifecyclePolicy,
		ComponentKindPermission:                    (*sourceBackedComponentBuildContext).buildPermission,
		ComponentKindSystemPermissionGrants:        (*sourceBackedComponentBuildContext).buildSystemPermissionGrants,
		ComponentKindOAuthCustomScope:              (*sourceBackedComponentBuildContext).buildOAuthCustomScope,
		ComponentKindConnectedApp:                  (*sourceBackedComponentBuildContext).buildConnectedApp,
		ComponentKindValidationRule:                (*sourceBackedComponentBuildContext).buildValidationRule,
		ComponentKindRecordType:                    (*sourceBackedComponentBuildContext).buildRecordType,
		ComponentKindRecordTypePicklistValue:       (*sourceBackedComponentBuildContext).buildRecordTypePicklistValue,
		ComponentKindListFields:                    (*sourceBackedComponentBuildContext).buildListFields,
		ComponentKindAutoNumberFormat:              (*sourceBackedComponentBuildContext).buildAutoNumberFormat,
		ComponentKindCustomField:                   (*sourceBackedComponentBuildContext).buildCustomField,
		ComponentKindAutoNumberCounter:             (*sourceBackedComponentBuildContext).buildAutoNumberCounter,
		ComponentKindFieldMapping:                  (*sourceBackedComponentBuildContext).buildFieldMapping,
		ComponentKindFormulaFunctionBundle:         (*sourceBackedComponentBuildContext).buildFormulaFunctionBundle,
		ComponentKindChildCloneRules:               (*sourceBackedComponentBuildContext).buildChildCloneRules,
		ComponentKindFolderType:                    (*sourceBackedComponentBuildContext).buildFolderType,
		ComponentKindStandardFlow:                  (*sourceBackedComponentBuildContext).buildStandardFlow,
		ComponentKindEmailTemplate:                 (*sourceBackedComponentBuildContext).buildEmailTemplate,
		ComponentKindInvocableAction:               (*sourceBackedComponentBuildContext).buildInvocableAction,
		ComponentKindFlowActionCallHandler:         (*sourceBackedComponentBuildContext).buildFlowActionCallHandler,
		ComponentKindRelatedListRule:               (*sourceBackedComponentBuildContext).buildRelatedListRule,
		ComponentKindPageLayout:                    (*sourceBackedComponentBuildContext).buildPageLayout,
		ComponentKindLayoutAssignment:              (*sourceBackedComponentBuildContext).buildLayoutAssignment,
		ComponentKindReportType:                    (*sourceBackedComponentBuildContext).buildReportType,
		ComponentKindUIAction:                      (*sourceBackedComponentBuildContext).buildUIAction,
		ComponentKindSearchScopeOrder:              (*sourceBackedComponentBuildContext).buildSearchScopeOrder,
		ComponentKindSearchAllDefaultFields:        (*sourceBackedComponentBuildContext).buildSearchAllDefaultFields,
		ComponentKindHrefRewriteFields:             (*sourceBackedComponentBuildContext).buildHrefRewriteFields,
		ComponentKindWebhookSecret:                 (*sourceBackedComponentBuildContext).buildWebhookSecret,
		ComponentKindConnectorCapability:           (*sourceBackedComponentBuildContext).buildConnectorCapability,
		ComponentKindConnectorRateLimit:            (*sourceBackedComponentBuildContext).buildConnectorRateLimit,
		ComponentKindConnectorInboundHandlerConfig: (*sourceBackedComponentBuildContext).buildConnectorInboundHandlerConfig,
		ComponentKindConnectorInboundRoute:         (*sourceBackedComponentBuildContext).buildConnectorInboundRoute,
		ComponentKindConnectorCalloutProfile:       (*sourceBackedComponentBuildContext).buildConnectorCalloutProfile,
		ComponentKindConnectorFieldMapping:         (*sourceBackedComponentBuildContext).buildConnectorFieldMapping,
		ComponentKindConnectorPushContract:         (*sourceBackedComponentBuildContext).buildConnectorPushContract,
	} {
		contract, ok := contracts[kind]
		if !ok {
			continue
		}
		contract.sourceBuildHandler = handler
		contracts[kind] = contract
	}
}

func (b *sourceBackedComponentBuildContext) build(component ManifestComponent, payload []byte) error {
	if route, ok := b.externalRoutes[component.Kind]; ok {
		if route.handler == nil {
			return fmt.Errorf("component kind %q external build route has no handler", component.Kind)
		}
		// Capture the engine ManifestComponent so legacy typed sinks can reuse
		// it while the public request carries the complete dependency graph.
		b.currentComponent = component
		b.currentPayload = append([]byte(nil), payload...)
		b.currentExternalRoute = &route
		b.currentManagedDescriptorCount = 0
		b.currentManagedBuildErr = nil
		componentCountBefore := len(b.p.components)
		handlerContext := cp.ComponentBuildContext(b)
		managedRoute := route.registration != nil && (route.registration.ManagedProvider != nil || route.lifecycle != nil)
		if managedRoute {
			handlerContext = &managedOnlyComponentBuildContext{delegate: b}
		}
		err := route.handler(handlerContext, cp.ComponentBuildRequest{
			PackName:  b.p.Name(),
			Component: contractManifestComponent(component, payload),
			Payload:   payload,
		})
		managedCount, managedErr := b.currentManagedDescriptorCount, b.currentManagedBuildErr
		addedComponentCount := len(b.p.components) - componentCountBefore
		b.currentExternalRoute = nil
		b.currentPayload = nil
		if err != nil {
			return err
		}
		if managedErr != nil {
			return managedErr
		}
		if managedRoute && managedCount != 1 {
			return fmt.Errorf("component kind %q build handler added %d managed descriptors, want exactly one", component.Kind, managedCount)
		}
		if managedRoute && addedComponentCount != 1 {
			return fmt.Errorf("component kind %q build handler emitted %d pack components, want exactly one managed component", component.Kind, addedComponentCount)
		}
		return nil
	}
	contract, ok := ComponentKindContractFor(component.Kind)
	if !ok || contract.sourceBuildHandler == nil {
		return fmt.Errorf("component kind %q is not installable by WT3", component.Kind)
	}
	return contract.sourceBuildHandler(b, component, payload)
}

func contractManifestComponent(component ManifestComponent, resolvedPayload []byte) cp.ManifestComponent {
	return cp.ManifestComponent{
		Kind:          cp.ComponentKind(component.Kind),
		BusinessKey:   component.BusinessKey,
		Manageability: cp.Manageability(component.Manageability),
		Path:          component.Path,
		Payload:       append(json.RawMessage(nil), resolvedPayload...),
		DependsOn:     externalComponentReferences(component.DependsOn),
	}
}

func externalComponentReferences(references []ComponentReference) []cp.ComponentReference {
	out := make([]cp.ComponentReference, len(references))
	for i, reference := range references {
		out[i] = cp.ComponentReference{Kind: cp.ComponentKind(reference.Kind), BusinessKey: reference.BusinessKey, Required: reference.Required}
	}
	return out
}

func (b *sourceBackedComponentBuildContext) buildObjectSpec(component ManifestComponent, payload []byte) error {
	p := b.p
	var registrations *ComponentKindRegistrationSet
	if p.componentRegistrationSetConfigured {
		registrations = &p.componentRegistrationSet
	}
	businessKey := strings.TrimSpace(component.BusinessKey)
	specs := b.parsedObjectSpecs[businessKey]
	runtimePayload := payload
	if overlaidObjects := b.overlaidObjectSpecs[businessKey]; len(overlaidObjects) > 0 {
		nextRuntimePayload, err := sourceBackedObjectSpecRuntimePayload(payload, specs, overlaidObjects)
		if err != nil {
			return fmt.Errorf("%s %s effective payload: %w", component.Kind, component.BusinessKey, err)
		}
		runtimePayload = nextRuntimePayload
	}
	p.components = append(p.components, &sourceBackedObjectSpecComponent{
		packName:                    p.Name(),
		registrations:               registrations,
		manifestComponent:           component,
		payload:                     append([]byte(nil), payload...),
		specs:                       specs,
		packObjectSpecs:             b.packObjectSpecs,
		packObjectSchemas:           b.packObjectSchemas,
		fieldCompat:                 p.fieldCompat,
		allowOwnerlessObjectAPI:     p.allowOwnerlessObjectAPI,
		standaloneListFieldsObjects: b.standaloneListFieldsObjects,
		formulaFunctionBundles:      p.runtimeFormulaFunctionBundlesForValidation(),
	})
	p.runtimeObjectPayloads = append(p.runtimeObjectPayloads, append([]byte(nil), runtimePayload...))
	p.ownedObjectAPINames = append(p.ownedObjectAPINames, objectSpecAPINames(specs)...)
	return nil
}

func (b *sourceBackedComponentBuildContext) buildObjectRecordMutationPolicy(component ManifestComponent, payload []byte) error {
	packComponent, err := newSourceBackedObjectRecordMutationPolicyComponent(b.p.Name(), component, payload, b.objectSpecIndex)
	if err != nil {
		return err
	}
	policyRow, err := sourceBackedObjectRecordMutationPolicyProjection(payload, componentPayloadLabel(component))
	if err != nil {
		return err
	}
	b.objectRecordMutationPolicyBatch.declaredObjects = append(b.objectRecordMutationPolicyBatch.declaredObjects, policyRow.ObjectAPIName)
	b.p.components = append(b.p.components, packComponent)
	return nil
}

func (b *sourceBackedComponentBuildContext) buildObjectLifecyclePolicy(component ManifestComponent, payload []byte) error {
	packComponent, err := newSourceBackedObjectLifecyclePolicyComponent(b.p.Name(), component, payload, b.objectSpecIndex)
	if err != nil {
		return err
	}
	policyRow, err := sourceBackedObjectLifecyclePolicyProjection(payload, componentPayloadLabel(component))
	if err != nil {
		return err
	}
	b.objectLifecyclePolicyBatch.declaredObjects = append(b.objectLifecyclePolicyBatch.declaredObjects, policyRow.ObjectAPIName)
	b.p.components = append(b.p.components, packComponent)
	return nil
}

func (b *sourceBackedComponentBuildContext) buildPermission(component ManifestComponent, payload []byte) error {
	payloadLabel := componentPayloadLabel(component)
	packComponent, err := newSourceBackedPermissionComponent(b.p.Name(), component, payload)
	if err != nil {
		return err
	}
	permissionRows, err := seedrows.ParseStandardPermissions(payload, payloadLabel)
	if err != nil {
		return err
	}
	for _, row := range permissionRows {
		projection, err := marshalRuntimeStartupShallowProjection(runtimeStartupShallowPermissionProjection{
			APIName:     row.APIName,
			Label:       row.Label,
			Category:    row.Category,
			Description: row.Description,
			Requires:    row.Requires,
			OwnerPack:   b.p.Name(),
			IsSystem:    true,
			IsActive:    true,
		})
		if err != nil {
			return fmt.Errorf("permission %s startup shallow projection: %w", row.APIName, err)
		}
		b.p.appendRuntimeStartupShallowDeclaration(ComponentKindPermission, row.APIName, projection)
	}
	b.p.components = append(b.p.components, packComponent)
	return nil
}

func (b *sourceBackedComponentBuildContext) buildSystemPermissionGrants(component ManifestComponent, payload []byte) error {
	packComponent, declarations, err := newSourceBackedSystemPermissionGrantsComponent(b.p.Name(), component, payload)
	if err != nil {
		return err
	}
	if err := b.systemPermissionGrantBatch.add(component, declarations); err != nil {
		return err
	}
	b.p.components = append(b.p.components, packComponent)
	return nil
}

func (b *sourceBackedComponentBuildContext) buildOAuthCustomScope(component ManifestComponent, payload []byte) error {
	packComponent, err := newSourceBackedOAuthCustomScopeComponentWithCompat(b.p.Name(), component, payload, b.p.oauthCustomScopeCompat.allowLegacyMissingRouteFamilyPrefix)
	if err != nil {
		return err
	}
	b.p.components = append(b.p.components, packComponent)
	return nil
}

func (b *sourceBackedComponentBuildContext) buildConnectedApp(component ManifestComponent, payload []byte) error {
	_, _, projection, err := sourceBackedConnectedAppComponentModel(b.p.Name(), component, payload, b.connectedAppPublicScopePolicy)
	if err != nil {
		return err
	}
	declaration, err := sourceBackedConnectedAppDeclarationForProjection(b.p.Name(), projection.ID, projection)
	if err != nil {
		return err
	}
	b.p.connectedAppDeclarations = append(b.p.connectedAppDeclarations, declaration)
	b.p.connectedAppOwnershipKeys = append(b.p.connectedAppOwnershipKeys, connectedAppOwnershipKeyFromProjection(projection))
	packComponent, err := newSourceBackedConnectedAppComponentWithPolicyAndDeclaredIDs(b.p.Name(), component, payload, b.connectedAppPublicScopePolicy, b.connectedAppDeclaredIDs)
	if err != nil {
		return err
	}
	b.p.components = append(b.p.components, packComponent)
	return nil
}

func (b *sourceBackedComponentBuildContext) buildValidationRule(component ManifestComponent, payload []byte) error {
	payloadLabel := componentPayloadLabel(component)
	packComponent, keys, err := newSourceBackedValidationRuleComponent(b.p.Name(), component, payload)
	if err != nil {
		return err
	}
	projectionRows, _, err := sourceBackedValidationRuleProjection(payload, payloadLabel, b.p.Name())
	if err != nil {
		return err
	}
	b.p.components = append(b.p.components, packComponent)
	b.p.validationRuleKeys = append(b.p.validationRuleKeys, keys...)
	for _, row := range projectionRows {
		if !row.Active {
			b.p.appendRuntimeStartupShallowInactiveValidationRuleKey(
				JoinComponentBusinessKey(row.ObjectAPIName, row.Name),
			)
			continue
		}
		projection, err := marshalRuntimeStartupShallowProjection(row)
		if err != nil {
			return fmt.Errorf("validation_rule %s/%s startup shallow projection: %w", row.ObjectAPIName, row.Name, err)
		}
		b.p.appendRuntimeStartupShallowDeclaration(
			ComponentKindValidationRule,
			JoinComponentBusinessKey(row.ObjectAPIName, row.Name),
			projection,
		)
	}
	return nil
}

func (b *sourceBackedComponentBuildContext) buildRecordType(component ManifestComponent, payload []byte) error {
	payloadLabel := componentPayloadLabel(component)
	packComponent, err := newSourceBackedRecordTypeComponent(b.p.Name(), component, payload)
	if err != nil {
		return err
	}
	recordTypeRows, err := sourceBackedRecordTypeProjection(payload, payloadLabel, b.p.Name())
	if err != nil {
		return err
	}
	for _, row := range recordTypeRows {
		projection, err := marshalRuntimeStartupShallowProjection(row)
		if err != nil {
			return fmt.Errorf("record_type %s/%s startup shallow projection: %w", row.ObjectAPIName, row.DeveloperName, err)
		}
		b.p.appendRuntimeStartupShallowDeclaration(
			ComponentKindRecordType,
			JoinComponentBusinessKey(row.ObjectAPIName, row.DeveloperName),
			projection,
		)
	}
	b.p.components = append(b.p.components, packComponent)
	return nil
}

func (b *sourceBackedComponentBuildContext) buildRecordTypePicklistValue(component ManifestComponent, payload []byte) error {
	packComponent, err := newSourceBackedRecordTypePicklistValueComponent(
		b.p.Name(),
		component,
		payload,
		b.objectSpecIndex,
		b.packObjectSpecs,
		b.recordTypeIndex,
		b.rtpvBatch,
	)
	if err != nil {
		return err
	}
	b.p.components = append(b.p.components, packComponent)
	return nil
}

func (b *sourceBackedComponentBuildContext) buildListFields(component ManifestComponent, payload []byte) error {
	payloadLabel := componentPayloadLabel(component)
	packComponent, err := newSourceBackedListFieldsComponent(b.p.Name(), component, payload)
	if err != nil {
		return err
	}
	listFieldRows, err := sourceBackedListFieldsProjection(payload, payloadLabel)
	if err != nil {
		return err
	}
	for _, row := range listFieldRows {
		objectAPIName := strings.TrimSpace(row.ObjectAPIName)
		if _, locked := b.lockedObjectSpecListFields[strings.ToLower(objectAPIName)]; locked {
			return fmt.Errorf("%s list_fields %s conflicts with locked object_spec listFields for %s", b.p.Name(), component.BusinessKey, objectAPIName)
		}
		projection, err := marshalRuntimeStartupShallowProjection(runtimeStartupShallowListFieldsProjection{
			ObjectAPIName: row.ObjectAPIName,
			Fields:        row.Fields,
		})
		if err != nil {
			return fmt.Errorf("%s list_fields %s startup shallow projection: %w", b.p.Name(), row.ObjectAPIName, err)
		}
		b.p.appendRuntimeStartupShallowDeclaration(ComponentKindListFields, row.ObjectAPIName, projection)
	}
	b.p.components = append(b.p.components, packComponent)
	return nil
}

func (b *sourceBackedComponentBuildContext) buildAutoNumberFormat(component ManifestComponent, payload []byte) error {
	payloadLabel := componentPayloadLabel(component)
	packComponent, err := newSourceBackedAutoNumberFormatComponent(b.p.Name(), component, payload)
	if err != nil {
		return err
	}
	formatSeeds, _, err := sourceBackedAutoNumberFormatProjection(payload, payloadLabel)
	if err != nil {
		return err
	}
	for _, seed := range formatSeeds {
		b.p.appendRuntimeStartupShallowKey(ComponentKindAutoNumberFormat, JoinComponentBusinessKey(seed.ObjectName, seed.FieldApiName))
		b.p.appendRuntimeStartupShallowKey(ComponentKindAutoNumberCounter, JoinComponentBusinessKey(seed.ObjectName, seed.FieldApiName, seed.Scope))
	}
	b.p.components = append(b.p.components, packComponent)
	return nil
}

func (b *sourceBackedComponentBuildContext) buildCustomField(component ManifestComponent, payload []byte) error {
	packComponent, err := newSourceBackedCustomFieldComponent(b.p.Name(), component, payload, b.objectSpecIndex, b.packObjectSpecs)
	if err != nil {
		return err
	}
	b.customFieldDeclarations = append(b.customFieldDeclarations, packComponent.declared)
	b.p.components = append(b.p.components, packComponent)
	return nil
}

func (b *sourceBackedComponentBuildContext) buildAutoNumberCounter(component ManifestComponent, payload []byte) error {
	packComponent, err := newSourceBackedAutoNumberCounterComponent(b.p.Name(), component, payload)
	if err != nil {
		return err
	}
	b.p.components = append(b.p.components, packComponent)
	return nil
}

func (b *sourceBackedComponentBuildContext) buildFieldMapping(component ManifestComponent, payload []byte) error {
	payloadLabel := componentPayloadLabel(component)
	packComponent, err := newSourceBackedFieldMappingComponent(b.p.Name(), component, payload)
	if err != nil {
		return err
	}
	groups, err := parseFieldMappingGroupsPayload(payload)
	if err != nil {
		return fmt.Errorf("parse %s: %w", payloadLabel, err)
	}
	for _, group := range groups {
		for _, mapping := range group.Mappings {
			b.p.appendRuntimeStartupShallowKey(
				ComponentKindFieldMapping,
				JoinComponentBusinessKey(group.SourceObject, group.TargetObject, group.ActionType, mapping.SourceField),
			)
		}
	}
	b.p.components = append(b.p.components, packComponent)
	return nil
}

func (b *sourceBackedComponentBuildContext) buildFormulaFunctionBundle(component ManifestComponent, payload []byte) error {
	packComponent, _, err := newSourceBackedFormulaFunctionBundleComponent(b.p.Name(), component, payload)
	if err != nil {
		return err
	}
	b.p.components = append(b.p.components, packComponent)
	return nil
}

func (b *sourceBackedComponentBuildContext) buildChildCloneRules(component ManifestComponent, payload []byte) error {
	if b.seenChildCloneRules {
		return fmt.Errorf("pack %q declares more than one child_clone_rules component; the current object_spec overlay supports exactly one per pack (component %q)", b.p.Name(), strings.TrimSpace(component.BusinessKey))
	}
	packComponent, err := newSourceBackedChildCloneRulesComponent(b.p.Name(), component, payload, b.objectSpecIndex)
	if err != nil {
		return err
	}
	b.p.components = append(b.p.components, packComponent)
	b.seenChildCloneRules = true
	return nil
}

func (b *sourceBackedComponentBuildContext) buildFolderType(component ManifestComponent, payload []byte) error {
	if b.seenFolderTypes {
		return fmt.Errorf("pack %q declares more than one folder_type component; the current _System_FolderType projection supports exactly one catalog component per pack (component %q)", b.p.Name(), strings.TrimSpace(component.BusinessKey))
	}
	packComponent, err := newSourceBackedFolderTypeComponentWithCompat(b.p.Name(), component, payload, b.p.folderTypeCompat)
	if err != nil {
		return err
	}
	expectedAny, err := packComponent.ExpectedProjection()
	if err != nil {
		return fmt.Errorf("%s %s projection: %w", component.Kind, component.BusinessKey, err)
	}
	expectedRows, ok := expectedAny.([]sourceBackedFolderTypeRow)
	if !ok {
		return fmt.Errorf("%s %s projection type %T, want []sourceBackedFolderTypeRow", component.Kind, component.BusinessKey, expectedAny)
	}
	b.folderTypeRows = append(b.folderTypeRows, expectedRows...)
	b.p.components = append(b.p.components, packComponent)
	b.seenFolderTypes = true
	return nil
}

func (b *sourceBackedComponentBuildContext) buildStandardFlow(component ManifestComponent, payload []byte) error {
	flowFileName := sourceBackedFlowFileName(component)
	packComponent, err := newSourceBackedStandardFlowComponent(
		b.p.Name(),
		component,
		payload,
		b.p.standardFlowPayloads,
		b.packObjectSpecs,
		b.p.runtimeFormulaFunctionBundlesForValidation(),
	)
	if err != nil {
		return err
	}
	b.p.appendRuntimeStartupShallowKey(ComponentKindStandardFlow, strings.TrimSpace(strings.TrimSuffix(flowFileName, ".flow.json")))
	b.p.components = append(b.p.components, packComponent)
	return nil
}

func (b *sourceBackedComponentBuildContext) buildEmailTemplate(component ManifestComponent, payload []byte) error {
	packComponent, err := newSourceBackedEmailTemplateComponent(b.p.Name(), component, payload)
	if err != nil {
		return err
	}
	b.emailTemplateBatch.declaredDeveloperNames = append(b.emailTemplateBatch.declaredDeveloperNames, packComponent.projection.DeveloperName)
	b.p.components = append(b.p.components, packComponent)
	return nil
}

func (b *sourceBackedComponentBuildContext) buildInvocableAction(component ManifestComponent, payload []byte) error {
	payloadLabel := componentPayloadLabel(component)
	packComponent, err := newSourceBackedInvocableActionComponent(b.p.Name(), component, payload, b.p.standardFlowPayloads)
	if err != nil {
		return err
	}
	actions, err := parseStandardActionPayload(payload, payloadLabel)
	if err != nil {
		return err
	}
	for _, action := range actions {
		b.p.appendRuntimeStartupShallowKey(ComponentKindInvocableAction, action.Name)
	}
	b.p.components = append(b.p.components, packComponent)
	return nil
}

func (b *sourceBackedComponentBuildContext) buildFlowActionCallHandler(component ManifestComponent, payload []byte) error {
	packComponent, err := newSourceBackedFlowActionCallHandlerComponent(b.p.Name(), component, payload, b.p.flowActionCallRegistrations...)
	if err != nil {
		return err
	}
	if err := validateFlowActionCallHandlerRuntimeRegistration(packComponent.projection, b.p.flowActionCallRegistrations...); err != nil {
		return err
	}
	projection, err := packComponent.ExpectedProjection()
	if err != nil {
		return err
	}
	projectionJSON, err := marshalRuntimeStartupShallowProjection(projection)
	if err != nil {
		return fmt.Errorf("flow_action_call_handler %s startup shallow projection: %w", packComponent.BusinessKey(), err)
	}
	b.flowActionCallHandlerBatch.declaredActionTypes = append(b.flowActionCallHandlerBatch.declaredActionTypes, packComponent.projection.ActionType)
	b.p.appendRuntimeStartupShallowDeclaration(ComponentKindFlowActionCallHandler, packComponent.BusinessKey(), projectionJSON)
	b.p.components = append(b.p.components, packComponent)
	return nil
}
