package toolingplan

import (
	"context"
	"os"
	"path/filepath"
	"sort"
)

type InstallStep struct {
	Tool    string
	Version string
	Source  string
	Reason  string
}

type SkipNote struct {
	Target string
	Reason string
}

type Plan struct {
	RepoManagedTools []string
	Steps            []InstallStep
	Skips            []SkipNote
}

type detector interface {
	detect(context.Context, string, map[string]struct{}) detectionResult
}

type detectionResult struct {
	Steps []InstallStep
	Skips []SkipNote
}

var detectors = []detector{
	goDetector{},
	nodeDetector{},
	pythonDetector{},
	rustDetector{},
}

func Build(ctx context.Context, repoRoot string) Plan {
	managedTools, managedSkips := repoManagedTools(repoRoot)
	steps := make([]InstallStep, 0)
	skips := append([]SkipNote(nil), managedSkips...)
	for _, detector := range detectors {
		result := detector.detect(ctx, repoRoot, managedTools)
		steps = append(steps, result.Steps...)
		skips = append(skips, result.Skips...)
	}
	sort.Slice(steps, func(i, j int) bool {
		if steps[i].Tool != steps[j].Tool {
			return steps[i].Tool < steps[j].Tool
		}
		if steps[i].Version != steps[j].Version {
			return steps[i].Version < steps[j].Version
		}
		return steps[i].Source < steps[j].Source
	})
	sort.Slice(skips, func(i, j int) bool {
		if skips[i].Target != skips[j].Target {
			return skips[i].Target < skips[j].Target
		}
		return skips[i].Reason < skips[j].Reason
	})
	repoManagedList := make([]string, 0, len(managedTools))
	for tool := range managedTools {
		repoManagedList = append(repoManagedList, tool)
	}
	sort.Strings(repoManagedList)
	return Plan{
		RepoManagedTools: repoManagedList,
		Steps:            steps,
		Skips:            skips,
	}
}

func readRepoFile(repoRoot, relativePath string) (string, bool, error) {
	data, err := os.ReadFile(filepath.Join(repoRoot, relativePath))
	if err != nil {
		if os.IsNotExist(err) {
			return "", false, nil
		}
		return "", false, err
	}
	return string(data), true, nil
}

func alreadyManaged(tool string, managedTools map[string]struct{}) bool {
	_, ok := managedTools[tool]
	return ok
}
