//go:build unix

package proc

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

// ConfigureProcessGroup makes cmd launch in its own process group so signals
// sent to the parent do not propagate automatically, letting the Manager
// control shutdown explicitly via Stop/StopAll.
func ConfigureProcessGroup(cmd *exec.Cmd) {
	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
}

type processKiller func(pid int, signal syscall.Signal) error

func terminateProcess(pid int, done <-chan struct{}, stopTimeout time.Duration) {
	if pid <= 0 {
		return
	}
	if pgid, ok := safeProcessGroupID(pid); ok && pgid != syscall.Getpgrp() {
		terminateProcessGroup(pgid, done, syscall.Kill, stopTimeout)
		return
	}
	terminatePIDTree(pid, done, syscall.Kill, stopTimeout)
}

func forceKillProcess(pid int) {
	if pid <= 0 {
		return
	}
	if pgid, ok := safeProcessGroupID(pid); ok && pgid != syscall.Getpgrp() {
		_ = syscall.Kill(-pgid, syscall.SIGKILL)
		return
	}
	for _, target := range collectDescendantPIDs(pid, ListProcesses()) {
		_ = syscall.Kill(target, syscall.SIGKILL)
	}
	_ = syscall.Kill(pid, syscall.SIGKILL)
}

func terminateProcessGroup(pgid int, done <-chan struct{}, kill processKiller, stopTimeout time.Duration) {
	if pgid <= 0 || pgid == syscall.Getpgrp() {
		return
	}
	if stopTimeout == 0 {
		stopTimeout = DefaultStopTimeout
	}
	_ = kill(-pgid, syscall.SIGTERM)
	if waitDone(done, stopTimeout) {
		return
	}
	_ = kill(-pgid, syscall.SIGKILL)
	waitDone(done, time.Second)
}

func terminatePIDTree(pid int, done <-chan struct{}, kill processKiller, stopTimeout time.Duration) {
	if stopTimeout == 0 {
		stopTimeout = DefaultStopTimeout
	}
	pids := collectDescendantPIDs(pid, ListProcesses())
	pids = append(pids, pid)
	for _, target := range pids {
		_ = kill(target, syscall.SIGTERM)
	}
	if waitDone(done, stopTimeout) {
		return
	}
	for _, target := range pids {
		_ = kill(target, syscall.SIGKILL)
	}
	waitDone(done, time.Second)
}

func safeProcessGroupID(pid int) (int, bool) {
	pgid, err := syscall.Getpgid(pid)
	if err != nil || pgid <= 0 || pgid == syscall.Getpgrp() {
		return 0, false
	}
	return pgid, true
}
