package toolingplan

import (
	"context"
	"fmt"
	"strings"
)

type nodeDetector struct{}

func (nodeDetector) detect(_ context.Context, repoRoot string, managedTools map[string]struct{}) detectionResult {
	result := detectionResult{}

	nodeVersion, nodeSource, nodeManaged, nodeSkip := detectNodeVersion(repoRoot, managedTools)
	if nodeManaged {
		result.Skips = append(result.Skips, SkipNote{Target: "node", Reason: "already managed by repo mise declarations"})
	} else if nodeVersion != "" {
		result.Steps = append(result.Steps, InstallStep{Tool: "node", Version: nodeVersion, Source: nodeSource, Reason: "exact runtime version"})
	} else {
		result.Skips = append(result.Skips, SkipNote{Target: "node", Reason: nodeSkip})
	}

	packageManagerVersion, packageManagerTool, packageManagerSource, packageManagerSkip := detectNodePackageManager(repoRoot)
	if packageManagerTool == "" {
		if packageManagerSkip != "" {
			result.Skips = append(result.Skips, SkipNote{Target: "node package manager", Reason: packageManagerSkip})
		}
		return result
	}
	if alreadyManaged(packageManagerTool, managedTools) {
		result.Skips = append(result.Skips, SkipNote{Target: "node package manager", Reason: packageManagerTool + " is already managed by repo mise declarations"})
		return result
	}
	if nodeVersion == "" && !alreadyManaged("node", managedTools) {
		result.Skips = append(result.Skips, SkipNote{Target: "node package manager", Reason: "packageManager is pinned but node is not pinned"})
		return result
	}
	result.Steps = append(result.Steps, InstallStep{Tool: packageManagerTool, Version: packageManagerVersion, Source: packageManagerSource, Reason: "exact packageManager version"})
	return result
}

func detectNodeVersion(repoRoot string, managedTools map[string]struct{}) (version string, source string, managed bool, skip string) {
	if alreadyManaged("node", managedTools) {
		return "", "", true, ""
	}
	for _, candidate := range []string{".node-version", ".nvmrc"} {
		value, ok, err := readRepoFile(repoRoot, candidate)
		if err != nil {
			return "", "", false, fmt.Sprintf("could not read %s: %v", candidate, err)
		}
		if !ok {
			continue
		}
		version, ok := normalizeExactVersion(strings.TrimSpace(value))
		if ok {
			return version, candidate, false, ""
		}
		return "", "", false, candidate + " does not pin an exact version"
	}
	packageJSON, ok, err := readRepoFile(repoRoot, "package.json")
	if err != nil {
		return "", "", false, fmt.Sprintf("could not read package.json: %v", err)
	}
	if !ok {
		return "", "", false, "no pinned node version file"
	}
	meta, err := parsePackageJSON(packageJSON)
	if err != nil {
		return "", "", false, fmt.Sprintf("could not parse package.json: %v", err)
	}
	if version, ok := normalizeExactVersion(meta.Volta.Node); ok {
		return version, "package.json#volta.node", false, ""
	}
	if strings.TrimSpace(meta.Volta.Node) != "" {
		return "", "", false, "package.json#volta.node is not an exact version"
	}
	return "", "", false, "no pinned node version file"
}

func detectNodePackageManager(repoRoot string) (version string, tool string, source string, skip string) {
	packageJSON, ok, err := readRepoFile(repoRoot, "package.json")
	if err != nil {
		return "", "", "", fmt.Sprintf("could not read package.json: %v", err)
	}
	if !ok {
		return "", "", "", ""
	}
	meta, err := parsePackageJSON(packageJSON)
	if err != nil {
		return "", "", "", fmt.Sprintf("could not parse package.json: %v", err)
	}
	value := strings.TrimSpace(meta.PackageManager)
	if value == "" {
		return "", "", "", ""
	}
	parts := strings.SplitN(value, "@", 2)
	if len(parts) != 2 {
		return "", "", "", "packageManager is not in tool@version form"
	}
	tool = strings.TrimSpace(parts[0])
	if tool != "pnpm" && tool != "yarn" && tool != "npm" && tool != "bun" {
		return "", "", "", "packageManager is not a supported exact installer target"
	}
	version, ok = normalizeExactVersion(parts[1])
	if !ok {
		return "", "", "", "packageManager version is not exact"
	}
	return version, tool, "package.json#packageManager", ""
}
