package core

import "fmt"

// DagTopoSort performs a topological sort of DAG steps using Kahn's algorithm.
// Returns levels where each level contains steps that can run in parallel.
// Returns an error if a dependency cycle is detected.
func DagTopoSort(steps []DagStep) ([][]DagStep, error) {
	if len(steps) == 0 {
		return nil, nil
	}

	// Build index: ref -> DagStep.
	index := make(map[string]DagStep, len(steps))
	for _, s := range steps {
		index[stepRef(s)] = s
	}

	// Compute in-degree for each step.
	inDegree := make(map[string]int, len(steps))
	for _, s := range steps {
		ref := stepRef(s)
		if _, ok := inDegree[ref]; !ok {
			inDegree[ref] = 0
		}
		for range s.Depends {
			inDegree[ref]++ // ref depends on a dep, so ref gets +1
		}
	}

	// Build adjacency list: dep -> list of steps that depend on dep.
	adj := make(map[string][]string, len(steps))
	for _, s := range steps {
		ref := stepRef(s)
		for _, dep := range s.Depends {
			adj[dep] = append(adj[dep], ref)
		}
	}

	// Initialize queue with steps that have in-degree 0.
	var queue []string
	for _, s := range steps {
		ref := stepRef(s)
		if inDegree[ref] == 0 {
			queue = append(queue, ref)
		}
	}

	var levels [][]DagStep
	processed := 0

	for len(queue) > 0 {
		// The current queue forms a parallel level.
		level := make([]DagStep, 0, len(queue))
		nextQueue := []string{}

		for _, ref := range queue {
			level = append(level, index[ref])
			processed++
			// Reduce in-degree for dependents.
			for _, dependent := range adj[ref] {
				inDegree[dependent]--
				if inDegree[dependent] == 0 {
					nextQueue = append(nextQueue, dependent)
				}
			}
		}

		levels = append(levels, level)
		queue = nextQueue
	}

	if processed != len(steps) {
		return nil, fmt.Errorf("dag_topo_sort: cycle detected (%d of %d steps processed)", processed, len(steps))
	}

	return levels, nil
}
