package main

import (
	"flag"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
)

func main() {
	var bunDarwinAarch64 string
	var bunDarwinX64 string
	var bunLinuxAarch64 string
	var bunLinuxX64 string
	var bunWindowsX64 string
	var packageDir string
	var script string

	flag.StringVar(&bunDarwinAarch64, "bun-darwin-aarch64", "", "")
	flag.StringVar(&bunDarwinX64, "bun-darwin-x64", "", "")
	flag.StringVar(&bunLinuxAarch64, "bun-linux-aarch64", "", "")
	flag.StringVar(&bunLinuxX64, "bun-linux-x64", "", "")
	flag.StringVar(&bunWindowsX64, "bun-windows-x64", "", "")
	flag.StringVar(&packageDir, "package-dir", "", "")
	flag.StringVar(&script, "script", "dev", "")
	flag.Parse()

	require(packageDir != "", "missing --package-dir")

	bunPath, err := resolveBunBinary(bunDarwinAarch64, bunDarwinX64, bunLinuxAarch64, bunLinuxX64, bunWindowsX64)
	must(err)
	must(makeAbsolute(&bunPath))

	workspaceRoot := os.Getenv("BUILD_WORKSPACE_DIRECTORY")
	if workspaceRoot == "" {
		returnError("BUILD_WORKSPACE_DIRECTORY is required for frontend dev sessions")
	}

	packageRoot := filepath.Join(workspaceRoot, filepath.FromSlash(packageDir))
	if _, err := os.Stat(filepath.Join(packageRoot, "package.json")); err != nil {
		returnError("frontend package.json not found in workspace package dir: " + packageRoot)
	}

	environment := append([]string{}, os.Environ()...)
	environment = setPath(environment, buildPath(workspaceRoot, packageRoot))

	command := exec.Command(bunPath, "--bun", "run", script)
	command.Dir = packageRoot
	command.Env = environment
	command.Stdout = os.Stdout
	command.Stderr = os.Stderr
	command.Stdin = os.Stdin

	if err := command.Run(); err != nil {
		if exitError, ok := err.(*exec.ExitError); ok {
			os.Exit(exitError.ExitCode())
		}
		panic(err)
	}
}

func resolveBunBinary(darwinAarch64 string, darwinX64 string, linuxAarch64 string, linuxX64 string, windowsX64 string) (string, error) {
	switch runtime.GOOS + "/" + runtime.GOARCH {
	case "darwin/arm64":
		return darwinAarch64, nil
	case "darwin/amd64":
		return darwinX64, nil
	case "linux/arm64":
		return linuxAarch64, nil
	case "linux/amd64":
		return linuxX64, nil
	case "windows/amd64":
		return windowsX64, nil
	default:
		return "", fmt.Errorf("unsupported Bun exec platform: %s/%s", runtime.GOOS, runtime.GOARCH)
	}
}

func buildPath(workspaceRoot string, packageRoot string) string {
	entries := make([]string, 0, 4)
	for _, candidate := range []string{
		filepath.Join(packageRoot, "node_modules", ".bin"),
		filepath.Join(workspaceRoot, "node_modules", ".bin"),
		os.Getenv("PATH"),
	} {
		if candidate != "" {
			entries = append(entries, candidate)
		}
	}
	return strings.Join(entries, string(os.PathListSeparator))
}

func setPath(environment []string, pathValue string) []string {
	return setEnv(environment, "PATH", pathValue)
}

func setEnv(environment []string, key string, value string) []string {
	prefix := key + "="
	for index, entry := range environment {
		if strings.HasPrefix(entry, prefix) {
			environment[index] = prefix + value
			return environment
		}
	}
	return append(environment, prefix+value)
}

func makeAbsolute(path *string) error {
	if filepath.IsAbs(*path) {
		return nil
	}

	absolutePath, err := filepath.Abs(*path)
	if err != nil {
		return err
	}
	*path = absolutePath
	return nil
}

func returnError(message string) {
	fmt.Fprintln(os.Stderr, message)
	os.Exit(1)
}

func must(err error) {
	if err != nil {
		panic(err)
	}
}

func require(condition bool, message string) {
	if !condition {
		panic(message)
	}
}
