package runtime

import (
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"os/signal"
	"syscall"
)

// RunAttached runs the runtime with stdin/stdout/stderr attached, forwards
// signals to the child process, and returns the child's exit code (§4.4). A
// non-zero exit of the tool is not an error; only invocation failures are.
func (r *Runtime) RunAttached(args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) {
	cmd := exec.Command(r.bin, args...)
	cmd.Stdin = stdin
	cmd.Stdout = stdout
	cmd.Stderr = stderr
	if err := cmd.Start(); err != nil {
		return 0, fmt.Errorf("cannot start %s: %w", r.bin, err)
	}

	sig := make(chan os.Signal, 2)
	signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
	defer signal.Stop(sig)
	done := make(chan struct{})
	defer close(done)
	go func() {
		for {
			select {
			case s := <-sig:
				_ = cmd.Process.Signal(s)
			case <-done:
				return
			}
		}
	}()

	err := cmd.Wait()
	var exitErr *exec.ExitError
	if errors.As(err, &exitErr) {
		return exitErr.ExitCode(), nil
	}
	if err != nil {
		return 0, fmt.Errorf("waiting for %s: %w", r.bin, err)
	}
	return 0, nil
}
