package loader

import "fmt"

func ModuleDependencyOrder(mods map[string]*ModuleInfo, primaryModule string) ([]string, error) {
	if _, ok := mods[primaryModule]; !ok {
		return nil, fmt.Errorf("primary package %q not found", primaryModule)
	}

	visited := map[string]VisitedStatusKind{}
	order := []string{}
	var visit func(string) error
	visit = func(name string) error {
		switch visited[name] {
		case VisitedStatusVisiting:
			return fmt.Errorf("circular import detected at %q", name)
		case VisitedStatusDone:
			return nil
		}

		info, ok := mods[name]
		if !ok {
			return fmt.Errorf("package imports unknown package %q", name)
		}
		visited[name] = VisitedStatusVisiting
		for _, dependency := range info.Imports {
			if _, ok := mods[dependency]; !ok {
				return fmt.Errorf("package %q imports unknown package %q", name, dependency)
			}
			if err := visit(dependency); err != nil {
				return err
			}
		}
		visited[name] = VisitedStatusDone
		order = append(order, name)
		return nil
	}

	if err := visit(primaryModule); err != nil {
		return nil, err
	}
	return order, nil
}

func BuildDependencyGraph(mods map[string]*ModuleInfo) (map[string][]string, error) {
	graph := map[string][]string{}

	for name, info := range mods {
		graph[name] = info.Imports

		for _, dep := range info.Imports {
			if _, ok := mods[dep]; !ok {
				return nil, fmt.Errorf("package %q imports unknown package %q", name, dep)
			}
		}
	}

	return graph, nil
}

type VisitedStatusKind int

const (
	VisitedStatusUnseen VisitedStatusKind = iota
	VisitedStatusVisiting
	VisitedStatusDone
)

func TopoSort(graph map[string][]string) ([]string, error) {
	visited := map[string]VisitedStatusKind{}
	order := []string{}

	var visit func(string) error
	visit = func(n string) error {
		switch visited[n] {
		case VisitedStatusVisiting:
			return fmt.Errorf("circular import detected at %q", n)
		case VisitedStatusDone:
			return nil
		}

		visited[n] = VisitedStatusVisiting

		for _, dep := range graph[n] {
			if err := visit(dep); err != nil {
				return err
			}
		}

		visited[n] = VisitedStatusDone
		order = append(order, n)
		return nil
	}

	for node := range graph {
		if visited[node] == VisitedStatusUnseen {
			if err := visit(node); err != nil {
				return nil, err
			}
		}
	}

	return order, nil
}