package main

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

func main() {
	var binaryPath string
	var buildAssetsPath string
	var frontendURL string
	var iconPath string
	var mode string

	flag.StringVar(&binaryPath, "binary", "", "")
	flag.StringVar(&buildAssetsPath, "build-assets", "", "")
	flag.StringVar(&frontendURL, "frontend-url", "http://127.0.0.1:9245", "")
	flag.StringVar(&iconPath, "icon", "", "")
	flag.StringVar(&mode, "mode", "run", "")
	flag.Parse()

	require(binaryPath != "", "missing --binary")
	require(buildAssetsPath != "", "missing --build-assets")

	environment := os.Environ()
	if mode == "dev" && os.Getenv("FRONTEND_DEVSERVER_URL") == "" {
		environment = append(environment, "FRONTEND_DEVSERVER_URL="+frontendURL)
	}

	if runtime.GOOS == "darwin" {
		os.Exit(runDarwin(binaryPath, buildAssetsPath, iconPath, mode, environment))
	}

	command := exec.Command(binaryPath)
	command.Stdout = os.Stdout
	command.Stderr = os.Stderr
	command.Stdin = os.Stdin
	command.Env = environment
	if err := command.Run(); err != nil {
		if exitError, ok := err.(*exec.ExitError); ok {
			os.Exit(exitError.ExitCode())
		}
		panic(err)
	}
}

func runDarwin(binaryPath string, buildAssetsPath string, iconPath string, mode string, environment []string) int {
	appName := strings.TrimSuffix(filepath.Base(binaryPath), filepath.Ext(binaryPath))
	if appName == "" {
		appName = "wails-app"
	}

	appDir := filepath.Join(os.TempDir(), appName+"-bazel-"+mode)
	defer os.RemoveAll(appDir)

	appContents := filepath.Join(appDir, "Contents")
	appMacOS := filepath.Join(appContents, "MacOS")
	appResources := filepath.Join(appContents, "Resources")
	appBinary := filepath.Join(appMacOS, appName)

	must(os.MkdirAll(appMacOS, 0o755))
	must(os.MkdirAll(appResources, 0o755))
	must(copyFile(binaryPath, appBinary, 0o755))

	for _, candidate := range []string{
		filepath.Join(buildAssetsPath, "darwin", "Info.dev.plist"),
		filepath.Join(buildAssetsPath, "darwin", "Info.plist"),
	} {
		if mode != "dev" && strings.HasSuffix(candidate, "Info.dev.plist") {
			continue
		}
		if _, err := os.Stat(candidate); err == nil {
			must(copyFile(candidate, filepath.Join(appContents, "Info.plist"), 0o644))
			break
		}
	}

	if iconPath != "" {
		if _, err := os.Stat(iconPath); err == nil {
			must(copyFile(iconPath, filepath.Join(appResources, filepath.Base(iconPath)), 0o644))
		}
	}

	if _, err := os.Stat("/usr/bin/codesign"); err == nil {
		codesign := exec.Command("/usr/bin/codesign", "--force", "--deep", "--sign", "-", appDir)
		_ = codesign.Run()
	}

	command := exec.Command(appBinary)
	command.Stdout = os.Stdout
	command.Stderr = os.Stderr
	command.Stdin = os.Stdin
	command.Env = environment
	if err := command.Run(); err != nil {
		if exitError, ok := err.(*exec.ExitError); ok {
			return exitError.ExitCode()
		}
		panic(err)
	}

	return 0
}

func copyFile(sourcePath string, destinationPath string, mode os.FileMode) error {
	sourceFile, err := os.Open(sourcePath)
	if err != nil {
		return err
	}
	defer sourceFile.Close()

	destinationFile, err := os.Create(destinationPath)
	if err != nil {
		return err
	}
	defer destinationFile.Close()

	if _, err := io.Copy(destinationFile, sourceFile); err != nil {
		return err
	}

	return destinationFile.Chmod(mode)
}

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

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