package builder

import (
	"errors"
	"fmt"
	"os/exec"
	"runtime"
	"strings"

	"tinygo.org/x/go-llvm"
)

// Commands lists command alternatives for various operating systems. These
// commands may have a slightly different name across operating systems and
// distributions or may not even exist in $PATH, in which case absolute paths
// may be used.
var commands = map[string][]string{}

func init() {
	llvmMajor, _, _ := strings.Cut(llvm.Version, ".")
	commands["clang"] = []string{"clang-" + llvmMajor}
	commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"}
	commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"}
	commands["lldb"] = []string{"lldb-" + llvmMajor, "lldb"}
	// Add the path to a Homebrew-installed LLVM for ease of use (no need to
	// manually set $PATH).
	if runtime.GOOS == "darwin" {
		var prefix string
		switch runtime.GOARCH {
		case "amd64":
			prefix = "/usr/local/opt/llvm@" + llvmMajor + "/bin/"
		case "arm64":
			prefix = "/opt/homebrew/opt/llvm@" + llvmMajor + "/bin/"
		default:
			// unknown GOARCH
			panic(fmt.Sprintf("unknown GOARCH: %s on darwin", runtime.GOARCH))
		}
		commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
		commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
		commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
		commands["lldb"] = append(commands["lldb"], prefix+"lldb")
	}
	// Add the path for when LLVM was installed with the installer from
	// llvm.org, which by default doesn't add LLVM to the $PATH environment
	// variable.
	if runtime.GOOS == "windows" {
		commands["clang"] = append(commands["clang"], "clang", "C:\\Program Files\\LLVM\\bin\\clang.exe")
		commands["ld.lld"] = append(commands["ld.lld"], "lld", "C:\\Program Files\\LLVM\\bin\\lld.exe")
		commands["wasm-ld"] = append(commands["wasm-ld"], "C:\\Program Files\\LLVM\\bin\\wasm-ld.exe")
		commands["lldb"] = append(commands["lldb"], "C:\\Program Files\\LLVM\\bin\\lldb.exe")
	}
	// Add the path to LLVM installed from ports.
	if runtime.GOOS == "freebsd" {
		prefix := "/usr/local/llvm" + llvmMajor + "/bin/"
		commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
		commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
		commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
		commands["lldb"] = append(commands["lldb"], prefix+"lldb")
	}
}

// LookupCommand looks up the executable name for a given LLVM tool such as
// clang or wasm-ld. It returns the (relative) command that can be used to
// invoke the tool or an error if it could not be found.
func LookupCommand(name string) (string, error) {
	for _, cmdName := range commands[name] {
		_, err := exec.LookPath(cmdName)
		if err != nil {
			if errors.Unwrap(err) == exec.ErrNotFound {
				continue
			}
			return cmdName, err
		}
		return cmdName, nil
	}
	return "", errors.New("none of these commands were found in your $PATH: " + strings.Join(commands[name], " "))
}
