package analytics

import (
	"context"
	"fmt"
	"sort"
	"strings"
)

// OrderForBuild topologically sorts objectAPINames so each object appears
// AFTER every object in its DerivedViewDependencies that is also in the
// candidate set. Returns:
//   - empty slice + nil for empty input
//   - named cycle error for circular dependencies (mirrors
//     source_backed_pack.go:379 "circular derived-view dependency" pattern)
//   - ErrFormulaViewMetadataNotReady for missing metadata (propagated from
//     DerivedViewDependencies)
//
// Objects that depend on something outside the candidate set are handled
// correctly: the out-of-set dependency is not a sort-ordering constraint
// within this batch.
//
// Deterministic output: the ready queue is processed in lexical order so
// the same input always produces the same output regardless of input order.
//
// NO lexical / pack-payload / raw-schema fallback under any condition.
// Per CLAUDE.md "fail closed" rule: cycle = error, missing metadata = error.
func (s *FormulaViewService) OrderForBuild(
	ctx context.Context,
	objectAPINames []string,
) ([]string, error) {
	if len(objectAPINames) == 0 {
		return nil, nil
	}

	// Build canonical name map: lower-key → original API name.
	// Deduplicates case-insensitively.
	canonicalName := make(map[string]string, len(objectAPINames))
	for _, name := range objectAPINames {
		key := strings.ToLower(name)
		if _, exists := canonicalName[key]; !exists {
			canonicalName[key] = name
		}
	}

	// Resolve dependencies for every candidate, restricting edges to those
	// within the candidate set (out-of-set deps are ignored for sort order).
	inDegree := make(map[string]int, len(canonicalName))
	// dependents[x] = list of keys that depend on x (i.e. x must come before them)
	dependents := make(map[string][]string, len(canonicalName))
	for key := range canonicalName {
		inDegree[key] = 0
	}

	for key, apiName := range canonicalName {
		deps, err := s.DerivedViewDependencies(ctx, apiName)
		if err != nil {
			return nil, err
		}
		for _, dep := range deps {
			depKey := strings.ToLower(dep)
			if _, inSet := canonicalName[depKey]; !inSet {
				// Dependency is outside the candidate set — not a sort constraint here.
				continue
			}
			if depKey == key {
				continue // self-dep guard (already excluded by DerivedViewDependencies but be safe)
			}
			inDegree[key]++
			dependents[depKey] = append(dependents[depKey], key)
		}
	}

	// Kahn's algorithm with lexical queue ordering for determinism.
	available := make([]string, 0, len(canonicalName))
	for key, deg := range inDegree {
		if deg == 0 {
			available = append(available, canonicalName[key])
		}
	}
	sort.Strings(available)

	ordered := make([]string, 0, len(canonicalName))
	for len(available) > 0 {
		// Pop lexically smallest ready node.
		name := available[0]
		available = available[1:]
		ordered = append(ordered, name)

		key := strings.ToLower(name)
		for _, depKey := range dependents[key] {
			inDegree[depKey]--
			if inDegree[depKey] == 0 {
				available = append(available, canonicalName[depKey])
				sort.Strings(available)
			}
		}
	}

	if len(ordered) != len(canonicalName) {
		// Cycle detected: collect remaining nodes with in-degree > 0.
		remaining := make([]string, 0, len(canonicalName)-len(ordered))
		for key, deg := range inDegree {
			if deg > 0 {
				remaining = append(remaining, canonicalName[key])
			}
		}
		sort.Strings(remaining)
		return nil, fmt.Errorf("circular derived-view dependency: %s", strings.Join(remaining, " -> "))
	}

	return ordered, nil
}
