package proc

import (
	"os"
	"os/exec"
	"time"
)

// KillProcess sends SIGTERM to p's process group, escalating to SIGKILL after
// StopTimeout. Safe to call on a nil process.
func KillProcess(p *os.Process, done <-chan struct{}, stopTimeout time.Duration) {
	if p == nil {
		return
	}
	TerminateProcess(p.Pid, done, stopTimeout)
}

// TerminateProcess kills the process group or PID tree rooted at pid.
func TerminateProcess(pid int, done <-chan struct{}, stopTimeout time.Duration) {
	terminateProcess(pid, done, stopTimeout)
}

// ForceKillProcess sends SIGKILL to the process group or PID tree rooted at
// pid without a graceful SIGTERM step. It is the "second Ctrl+C" path for
// callers that need to exit immediately and cannot wait for graceful drain.
func ForceKillProcess(pid int) {
	forceKillProcess(pid)
}

func waitDone(done <-chan struct{}, timeout time.Duration) bool {
	timer := time.NewTimer(timeout)
	defer timer.Stop()
	select {
	case <-done:
		return true
	case <-timer.C:
		return false
	}
}

// WaitPIDGone polls until pid is no longer alive or timeout elapses.
func WaitPIDGone(pid int, timeout time.Duration) bool {
	deadline := time.Now().Add(timeout)
	for time.Now().Before(deadline) {
		if !PIDAlive(pid) {
			return true
		}
		time.Sleep(100 * time.Millisecond)
	}
	return false
}

// KillCmd terminates cmd's process group, escalating to SIGKILL.
func KillCmd(cmd *exec.Cmd, stopTimeout time.Duration) {
	if cmd == nil || cmd.Process == nil {
		return
	}
	done := make(chan struct{})
	go func() {
		_ = cmd.Wait()
		close(done)
	}()
	TerminateProcess(cmd.Process.Pid, done, stopTimeout)
}
