package packs

import (
	"fmt"
	"strings"

	"github.com/nexuscrm/files"
	kernelconnector "github.com/nexuscrm/kernel/pkg/connector"
)

func (p *SourceBackedPack) buildComponents() error {
	ordered, err := topologicallySortManifestComponents(p.source, p.manifest.Components)
	if err != nil {
		return err
	}
	if err := p.collectSourceBackedRuntimePayloads(ordered); err != nil {
		return err
	}
	if err := p.collectSourceBackedFormulaFunctionBundles(ordered); err != nil {
		return err
	}
	objectSpecs, err := p.collectSourceBackedObjectSpecBuildState(ordered)
	if err != nil {
		return err
	}
	lockedObjectSpecListFields := lockedObjectSpecListFieldsObjects(objectSpecs.parsedByBusinessKey, p.manifest.Components)
	standaloneListFieldsObjects, err := sourceBackedStandaloneListFieldsObjects(p.source, ordered)
	if err != nil {
		return err
	}
	recordTypeIndex, err := buildSourceBackedRecordTypeIndex(p.source, ordered)
	if err != nil {
		return err
	}
	rtpvBatch, err := collectSourceBackedRecordTypePicklistValueBatch(p.source, ordered)
	if err != nil {
		return err
	}
	pageLayoutBatch := &sourceBackedPageLayoutPackBatch{}
	layoutAssignmentBatch := newSourceBackedLayoutAssignmentPackBatch()
	reportTypeBatch := &sourceBackedReportTypePackBatch{}
	emailTemplateBatch := &emailTemplatePackBatch{}
	webhookSecretBatch := &webhookSecretPackBatch{}
	connectorRateLimitBatch := &connectorRateLimitPackBatch{}
	objectRecordMutationPolicyBatch := &objectRecordMutationPolicyPackBatch{}
	objectLifecyclePolicyBatch := &objectLifecyclePolicyPackBatch{}
	flowActionCallHandlerBatch := &flowActionCallHandlerPackBatch{}
	systemPermissionGrantBatch := &systemPermissionGrantPackBatch{}
	p.connectorInboundHandlerConfigBatch = &connectorInboundHandlerConfigBatch{}
	p.connectorInboundRouteBatch = &connectorInboundRouteBatch{}
	connectorCalloutProfileBatch := &connectorCalloutProfilePackBatch{}
	connectedAppPublicScopePolicy, err := sourceBackedConnectedAppPublicScopePolicyFromSource(p.Name(), p.source, p.oauthCustomScopeCompat.allowLegacyMissingRouteFamilyPrefix)
	if err != nil {
		return err
	}
	connectedAppDeclaredIDs, err := sourceBackedConnectedAppDeclaredIDSetFromSource(p.Name(), p.source, connectedAppPublicScopePolicy)
	if err != nil {
		return err
	}
	packObjectSchemas := frozenFormulaSchemasFromObjectSpecs(objectSpecs.packSpecs)
	if err := p.appendObjectSpecListFieldsStartupShallowDeclarations(objectSpecs.packSpecs); err != nil {
		return err
	}
	if err := p.precollectSourceBackedLayoutSearchMetadata(ordered, objectSpecs.packSpecs); err != nil {
		return err
	}
	buildCtx := &sourceBackedComponentBuildContext{
		p:                               p,
		parsedObjectSpecs:               objectSpecs.parsedByBusinessKey,
		overlaidObjectSpecs:             objectSpecs.overlaidByBusinessKey,
		objectSpecIndex:                 objectSpecs.indexByObjectAPIName,
		packObjectSpecs:                 objectSpecs.packSpecs,
		packObjectSchemas:               packObjectSchemas,
		standaloneListFieldsObjects:     standaloneListFieldsObjects,
		lockedObjectSpecListFields:      lockedObjectSpecListFields,
		recordTypeIndex:                 recordTypeIndex,
		rtpvBatch:                       rtpvBatch,
		pageLayoutBatch:                 pageLayoutBatch,
		layoutAssignmentBatch:           layoutAssignmentBatch,
		reportTypeBatch:                 reportTypeBatch,
		emailTemplateBatch:              emailTemplateBatch,
		webhookSecretBatch:              webhookSecretBatch,
		connectorRateLimitBatch:         connectorRateLimitBatch,
		objectRecordMutationPolicyBatch: objectRecordMutationPolicyBatch,
		objectLifecyclePolicyBatch:      objectLifecyclePolicyBatch,
		flowActionCallHandlerBatch:      flowActionCallHandlerBatch,
		connectorCalloutProfileBatch:    connectorCalloutProfileBatch,
		systemPermissionGrantBatch:      systemPermissionGrantBatch,
		connectedAppPublicScopePolicy:   connectedAppPublicScopePolicy,
		connectedAppDeclaredIDs:         connectedAppDeclaredIDs,
		externalRoutes:                  p.externalBuildRoutes,
	}
	for _, component := range ordered {
		payload, err := sourceBackedComponentPayload(p.source, component)
		if err != nil {
			return fmt.Errorf("%s %s payload: %w", component.Kind, component.BusinessKey, err)
		}
		if err := buildCtx.build(component, payload); err != nil {
			return err
		}
	}
	p.ownedObjectAPINames = uniqueSortedNonEmpty(p.ownedObjectAPINames)
	if p.stoppedSourceSetConstructionApplyOnly {
		for _, name := range p.ownedObjectAPINames {
			p.allowOwnerlessObjectAPI[strings.ToLower(name)] = true
		}
	}
	normalizedHrefFields := files.NormalizeHrefRewriteFields(p.hrefRewriteFields)
	if len(normalizedHrefFields) != len(p.hrefRewriteFields) {
		return fmt.Errorf("href_rewrite_fields declares duplicate or blank field pairs")
	}
	p.hrefRewriteFields = normalizedHrefFields
	if err := validateSourceBackedEmailTemplateDeveloperNames(emailTemplateBatch.declaredDeveloperNames); err != nil {
		return err
	}
	if err := validateConnectorFieldMappingNoCrossComponentDuplicates(p.connectorFieldMappingDeclarations); err != nil {
		return err
	}
	connectorPushValidationOptions := connectorPushValidationOptionsFromObjectSpecs(
		objectSpecs.packSpecs,
		buildCtx.customFieldDeclarations,
		p.connectorPushContractDeclarations,
		p.connectorPushValidationSchema,
	)
	if p.stoppedSourceSetConstructionApplyOnly {
		// The stopped source set was admitted against its exact dependency graph
		// before this apply-only reconstruction. Re-run the schema-independent
		// closure validation here so derived association target authority is part
		// of the immutable declaration without consulting live Runtime schema.
		connectorPushValidationOptions.ObjectFields = nil
		connectorPushValidationOptions.RequireSourceLookupFieldSchema = false
	}
	if err := kernelconnector.ValidateConnectorPushDurableMetadataContractsWithOptions(
		p.connectorPushContractDeclarations,
		p.connectorFieldMappingDeclarations,
		connectorPushValidationOptions,
	); err != nil {
		return err
	}
	if !p.stoppedSourceSetConstructionApplyOnly {
		p.connectorPushHistoricalBindings, err = resolveConnectorPushHistoricalReconciliationBindings(
			p.connectorPushHistoricalDecls,
			p.connectorFieldMappingDeclarations,
			connectorPushValidationOptions,
		)
		if err != nil {
			return err
		}
	}
	if err := p.appendConnectorPushContractRuntimeStartupShallowDeclarations(); err != nil {
		return err
	}
	p.appendSourceBackedSweepComponents(buildCtx)
	return nil
}

func (p *SourceBackedPack) appendSourceBackedSweepComponents(buildCtx *sourceBackedComponentBuildContext) {
	p.sweepComponents = append(p.sweepComponents, newRecordTypePicklistValuePackSweeperComponent(p.Name(), buildCtx.rtpvBatch))
	p.sweepComponents = append(p.sweepComponents, newSourceBackedLayoutAssignmentPackSweeperComponent(p.Name(), buildCtx.layoutAssignmentBatch))
	p.sweepComponents = append(p.sweepComponents, newSourceBackedPageLayoutPackSweeperComponent(p.Name(), buildCtx.pageLayoutBatch))
	if !p.componentRegistrationSet.HasManagedOwnerPackTable(ComponentKindReportType, "_System_ReportType") {
		p.sweepComponents = append(p.sweepComponents, newSourceBackedReportTypePackSweeperComponent(p.Name(), buildCtx.reportTypeBatch))
	}
	p.sweepComponents = append(p.sweepComponents, newEmailTemplatePackSweeperComponent(p.Name(), buildCtx.emailTemplateBatch))
	p.sweepComponents = append(p.sweepComponents, newWebhookSecretPackSweeperComponent(p.Name(), buildCtx.webhookSecretBatch))
	p.sweepComponents = append(p.sweepComponents, newSourceBackedFolderTypePackSweeperComponent(p.Name(), buildCtx.folderTypeRows))
	p.sweepComponents = append(p.sweepComponents, newConnectorCapabilityPackSweeperComponent(p.Name(), p.connectorCapabilityDeclarations))
	p.sweepComponents = append(p.sweepComponents, newSourceBackedConnectedAppPackSweeperComponent(p.Name(), p.connectedAppDeclarations))
	p.sweepComponents = append(p.sweepComponents, newConnectorRateLimitPackSweeperComponent(p.Name(), buildCtx.connectorRateLimitBatch))
	p.sweepComponents = append(p.sweepComponents, newConnectorInboundHandlerConfigPackSweeperComponent(p.Name(), p.connectorInboundHandlerConfigBatch.declaredCapabilityKinds))
	p.sweepComponents = append(p.sweepComponents, newConnectorInboundHandlerConfigIncarnationSweeper(p.Name()))
	p.sweepComponents = append(p.sweepComponents, newConnectorInboundRoutePackSweeperComponent(p.Name(), p.connectorInboundRouteBatch.declaredEventKinds))
	p.sweepComponents = append(p.sweepComponents, newConnectorInboundRouteIncarnationSweeper(p.Name()))
	p.sweepComponents = append(p.sweepComponents, newConnectorCalloutProfilePackSweeperComponent(p.Name(), buildCtx.connectorCalloutProfileBatch))
	p.sweepComponents = append(p.sweepComponents, newConnectorFieldMappingPackSweeperComponent(p.Name(), p.connectorFieldMappingDeclarations))
	p.sweepComponents = append(p.sweepComponents, newObjectRecordMutationPolicyPackSweeperComponent(p.Name(), buildCtx.objectRecordMutationPolicyBatch))
	p.sweepComponents = append(p.sweepComponents, newObjectLifecyclePolicyPackSweeperComponent(p.Name(), buildCtx.objectLifecyclePolicyBatch))
	p.sweepComponents = append(p.sweepComponents, newFlowActionCallHandlerPackSweeperComponent(p.Name(), buildCtx.flowActionCallHandlerBatch))
	p.sweepComponents = append(p.sweepComponents, newSourceBackedStandardFlowPackSweeperComponent(p.Name(), sourceBackedStandardFlowDeveloperNames(p.standardFlowPayloads)))
}

func sourceBackedStandardFlowDeveloperNames(payloads map[string][]byte) []string {
	developerNames := make([]string, 0, len(payloads))
	for flowFileName := range payloads {
		developerName := strings.TrimSpace(strings.TrimSuffix(flowFileName, ".flow.json"))
		if developerName != "" {
			developerNames = append(developerNames, developerName)
		}
	}
	return developerNames
}

func (p *SourceBackedPack) collectSourceBackedRuntimePayloads(components []ManifestComponent) error {
	p.standardFlowPayloads = map[string][]byte{}
	for _, component := range components {
		if component.Kind != ComponentKindStandardFlow {
			continue
		}
		payload, err := sourceBackedComponentPayload(p.source, component)
		if err != nil {
			return fmt.Errorf("%s %s payload: %w", component.Kind, component.BusinessKey, err)
		}
		p.standardFlowPayloads[sourceBackedFlowFileName(component)] = payload
	}
	return nil
}

func (p *SourceBackedPack) collectSourceBackedFormulaFunctionBundles(components []ManifestComponent) error {
	for _, component := range components {
		if component.Kind != ComponentKindFormulaFunctionBundle {
			continue
		}
		payload, err := sourceBackedComponentPayload(p.source, component)
		if err != nil {
			return fmt.Errorf("%s %s payload: %w", component.Kind, component.BusinessKey, err)
		}
		_, bundle, err := newSourceBackedFormulaFunctionBundleComponent(p.Name(), component, payload)
		if err != nil {
			return err
		}
		p.formulaFunctionBundles = append(p.formulaFunctionBundles, bundle)
	}
	return nil
}
