package graph

import (
	
	
	
)

// TopologicalSort runs a topological sort on a given directed graph and returns
// the vertex hashes in topological order. The topological order is a non-unique
// order of vertices in a directed graph where an edge from vertex A to vertex B
// implies that vertex A appears before vertex B.
//
// Note that TopologicalSort doesn't make any guarantees about the order. If there
// are multiple valid topological orderings, an arbitrary one will be returned.
// To make the output deterministic, use [StableTopologicalSort].
//
// TopologicalSort only works for directed acyclic graphs. This implementation
// works non-recursively and utilizes Kahn's algorithm.
func [ comparable,  any]( Graph[, ]) ([], error) {
	if !.Traits().IsDirected {
		return nil, fmt.Errorf("topological sort cannot be computed on undirected graph")
	}

	,  := .Order()
	if  != nil {
		return nil, fmt.Errorf("failed to get graph order: %w", )
	}

	,  := .PredecessorMap()
	if  != nil {
		return nil, fmt.Errorf("failed to get predecessor map: %w", )
	}

	 := make([], 0)

	for ,  := range  {
		if len() == 0 {
			 = append(, )
		}
	}

	 := make([], 0, )
	 := make(map[]struct{}, )

	for len() > 0 {
		 := [0]
		 = [1:]

		if ,  := [];  {
			continue
		}

		 = append(, )
		[] = struct{}{}

		for ,  := range  {
			delete(, )

			if len() == 0 {
				 = append(, )
			}
		}
	}

	if len() !=  {
		return nil, errors.New("topological sort cannot be computed on graph with cycles")
	}

	return , nil
}

// StableTopologicalSort does the same as [TopologicalSort], but takes a function
// for comparing (and then ordering) two given vertices. This allows for a stable
// and deterministic output even for graphs with multiple topological orderings.
func [ comparable,  any]( Graph[, ],  func(, ) bool) ([], error) {
	if !.Traits().IsDirected {
		return nil, fmt.Errorf("topological sort cannot be computed on undirected graph")
	}

	,  := .PredecessorMap()
	if  != nil {
		return nil, fmt.Errorf("failed to get predecessor map: %w", )
	}

	 := make([], 0)
	 := make(map[]struct{})

	for ,  := range  {
		if len() == 0 {
			 = append(, )
			[] = struct{}{}
		}
	}

	 := make([], 0, len())
	 := make(map[]struct{})

	sort.Slice(, func(,  int) bool {
		return ([], [])
	})

	for len() > 0 {
		 := [0]
		 = [1:]

		if ,  := [];  {
			continue
		}

		 = append(, )
		[] = struct{}{}

		 := make([], 0)

		for ,  := range  {
			delete(, )

			if len() != 0 {
				continue
			}

			if ,  := [];  {
				continue
			}

			 = append(, )
			[] = struct{}{}
		}

		sort.Slice(, func(,  int) bool {
			return ([], [])
		})

		 = append(, ...)
	}

	,  := .Order()
	if  != nil {
		return nil, fmt.Errorf("failed to get graph order: %w", )
	}

	if len() !=  {
		return nil, errors.New("topological sort cannot be computed on graph with cycles")
	}

	return , nil
}

// TransitiveReduction returns a new graph with the same vertices and the same
// reachability as the given graph, but with as few edges as possible. The graph
// must be a directed acyclic graph.
//
// TransitiveReduction is a very expensive operation scaling with O(V(V+E)).
func [ comparable,  any]( Graph[, ]) (Graph[, ], error) {
	if !.Traits().IsDirected {
		return nil, fmt.Errorf("transitive reduction cannot be performed on undirected graph")
	}

	,  := .Clone()
	if  != nil {
		return nil, fmt.Errorf("failed to clone the graph: %w", )
	}

	,  := .AdjacencyMap()
	if  != nil {
		return nil, fmt.Errorf("failed to get adajcency map: %w", )
	}

	// For each vertex in the graph, run a depth-first search from each direct
	// successor of that vertex. Then, for each vertex visited within the DFS,
	// inspect all of its edges. Remove the edges that also appear in the edge
	// set of the top-level vertex and target the current vertex. These edges
	// are redundant because their targets apparently are not only reachable
	// from the top-level vertex, but also through a DFS.
	for ,  := range  {
		,  := .Order()
		if  != nil {
			return nil, fmt.Errorf("failed to get graph order: %w", )
		}
		for  := range  {
			 := make([], 0, )
			 := make(map[]struct{}, )
			 := make(map[]bool, )

			 = append(, )

			for len() > 0 {
				 := [len()-1]
				 = [:len()-1]

				if ,  := [];  {
					[] = false
					continue
				}

				[] = struct{}{}
				[] = true
				 = append(, )

				if len([]) == 0 {
					[] = false
				}

				for  := range [] {
					if ,  := [];  {
						if [] {
							// If the current adjacency is both on the stack and
							// has already been visited, there is a cycle.
							return nil, fmt.Errorf("transitive reduction cannot be performed on graph with cycle")
						}
						continue
					}

					if ,  := [][];  {
						_ = .RemoveEdge(, )
					}
					 = append(, )
				}
			}
		}
	}

	return , nil
}