package packs

import (
	"strings"

	domainschema "github.com/nexuscrm/kernel/pkg/schema"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

// canonicalizeObjectKey is kept for backward compatibility with existing
// test helpers. The actual canonicalization is now in
// domainschema.ValidateRollupGraph.
func canonicalizeObjectKey(name string) string {
	return strings.ToLower(strings.TrimSpace(name))
}

// ValidateRollupCycles inspects the rollup-summary field declarations in the
// provided ObjectSpecs and rejects the pack apply if the rollup-relation graph
// contains a directed cycle (including self-rollups) or exceeds the maximum
// allowed chain depth.
//
// It converts ObjectSpecs to ObjectMetadata and delegates to the shared
// domainschema.ValidateRollupGraph so that pack/apply and metadata CRUD
// enforce the same rules.
func ValidateRollupCycles(specs []ObjectSpec) error {
	return ValidateRollupCyclesWithResolver(specs, objectSpecFieldResolver{})
}

// ValidateRollupCyclesWithResolver is the full authoring-time rollup graph
// validator. It merges rollup edges from specs and previously-installed
// objects in the resolver, converts them to ObjectMetadata, and delegates
// to domainschema.ValidateRollupGraph.
//
// This ensures pack/apply and metadata CRUD use the same cycle/depth
// validator — one SSoT for rollup graph validity.
func ValidateRollupCyclesWithResolver(specs []ObjectSpec, resolver objectSpecFieldResolver) error {
	// Convert specs + resolver schemas into a flat list of ObjectMetadata
	// that domainschema.ValidateRollupGraph can process.
	var allSchemas []*models.ObjectMetadata

	// Track which canonical keys appear in specs so resolver entries
	// overridden by specs are excluded (spec version is authoritative).
	specKeys := make(map[string]struct{}, len(specs))
	for _, spec := range specs {
		key := canonicalizeObjectKey(spec.APIName)
		if key != "" {
			specKeys[key] = struct{}{}
		}
	}

	// Add schemas from resolver (previously-installed objects),
	// excluding those overridden by specs.
	for key, schema := range resolver.schemasByObject {
		if schema == nil {
			continue
		}
		if _, inSpec := specKeys[key]; inSpec {
			continue
		}
		allSchemas = append(allSchemas, schema)
	}

	// Build ObjectMetadata from specs.
	for _, spec := range specs {
		fields := make([]models.FieldMetadata, 0, len(spec.Fields))
		for _, fSpec := range spec.Fields {
			field := models.FieldMetadata{
				APIName:      fSpec.APIName,
				Type:         constants.SchemaFieldType(fSpec.Type),
				RollupConfig: cloneNormalizedRollupConfigWithResolver(fSpec.RollupConfig, resolver),
			}
			fields = append(fields, field)
		}
		allSchemas = append(allSchemas, &models.ObjectMetadata{
			APIName: spec.APIName,
			Fields:  fields,
		})
	}

	// Delegate to the shared validator.
	return domainschema.ValidateRollupGraph(allSchemas, "")
}
