package loader

import (
	"fmt"

	"github.com/marzeq/qk/codegen/irgen"
	"github.com/marzeq/qk/ir"
	"github.com/marzeq/qk/parser"
	"github.com/marzeq/qk/sema"
	"github.com/marzeq/qk/shared"
)

func ComputeModuleOrder(mods map[string]*ModuleInfo) ([]string, []error) {
	graph, err := BuildDependencyGraph(mods)
	if err != nil {
		return nil, []error{err}
	}

	order, err := TopoSort(graph)
	if err != nil {
		return nil, []error{err}
	}

	return order, nil
}

func RunSemanticPipeline(mods map[string]*ModuleInfo, analyser *sema.Analyser, order []string, verbose, debug bool) []error {
	for _, name := range order {
		info := mods[name]

		for _, root := range info.Roots {
			analyser.AnalyseModule(root, name)
		}
	}

	if len(analyser.Errors()) > 0 {
		return analyser.Errors()
	}

	if verbose {
		fmt.Println("completed analysis phase")
	}

	attributor := analyser.NewAttributor()

	for _, name := range order {
		info := mods[name]

		for _, root := range info.Roots {
			attributor.AttributeModule(root)
		}
	}

	if len(attributor.Errors()) > 0 {
		return attributor.Errors()
	}

	if verbose {
		fmt.Println("completed attribution phase")
	}

	validator := analyser.NewValidator()

	for _, name := range order {
		info := mods[name]
		for _, root := range info.Roots {
			validator.ValidateModule(root)
		}
	}

	if len(validator.Errors()) > 0 {
		return validator.Errors()
	}

	if errs := checkExternConflicts(mods, order); len(errs) > 0 {
		return errs
	}

	if verbose {
		fmt.Println("completed validation phase")
	}

	if debug {
		for _, name := range order {
			info := mods[name]
			for _, root := range info.Roots {
				analyser.DebugCheck(root)
			}
		}

		if verbose {
			fmt.Println("completed debug checks")
		}
	}

	return nil
}

func GenerateIRModules(mods map[string]*ModuleInfo, order []string, verbose bool) (map[string]*ir.Module, []error) {
	irMods := map[string]*ir.Module{}

	for _, name := range order {
		info := mods[name]
		out := &ir.Module{}

		for _, root := range info.Roots {
			gen := &irgen.Generator{ModuleName: name}
			modIR := gen.Generate(root)
			out.Functions = append(out.Functions, modIR.Functions...)
			if len(modIR.Externs) > 0 {
				out.Externs = append(out.Externs, modIR.Externs...)
			}
		}

		irMods[name] = out
	}

	if verbose {
		fmt.Println("completed ir generation phase")
	}

	return irMods, nil
}

func checkExternConflicts(mods map[string]*ModuleInfo, order []string) []error {
	type firstExtern struct {
		module string
	}

	seen := make(map[string]firstExtern)
	var errs []error

	for _, moduleName := range order {
		info := mods[moduleName]
		if info == nil {
			continue
		}

		for _, root := range info.Roots {
			for _, node := range root.Body {
				fn, ok := node.(*parser.FunctionDefNode)
				if !ok || !fn.Extern {
					continue
				}

				if prev, exists := seen[fn.Name]; exists && prev.module != moduleName {
					errs = append(errs, shared.NewError(
						fn.GetLoc(),
						"extern function %q conflicts across modules %q and %q; extern functions are not mangled",
						fn.Name,
						prev.module,
						moduleName,
					))
					continue
				}

				if _, exists := seen[fn.Name]; !exists {
					seen[fn.Name] = firstExtern{module: moduleName}
				}
			}
		}
	}

	return errs
}