//go:build !windows

package proc

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

// unixGroup 用 POSIX 进程组实现整树终止：子进程经 Setpgid 成为新进程组组长
// （pgid == pid），向负 pgid 发信号即覆盖整个进程组的所有子孙。
type unixGroup struct {
	pgid int
}

func newGroup() Group { return &unixGroup{} }

func (g *unixGroup) Prepare(cmd *exec.Cmd) {
	if cmd.SysProcAttr == nil {
		cmd.SysProcAttr = &syscall.SysProcAttr{}
	}
	cmd.SysProcAttr.Setpgid = true
}

func (g *unixGroup) Adopt(cmd *exec.Cmd) error {
	if cmd.Process == nil {
		return errors.New("proc: Adopt before Start")
	}
	// Setpgid 使子进程成为新进程组组长，pgid == pid。
	g.pgid = cmd.Process.Pid
	return nil
}

func (g *unixGroup) TerminateGroup(done <-chan struct{}, grace time.Duration) {
	if g.pgid <= 0 {
		return
	}
	// 向负 pgid 发信号 = 向整个进程组（含所有子孙）发信号。忽略 ESRCH 等错误。
	_ = syscall.Kill(-g.pgid, syscall.SIGTERM)
	select {
	case <-done:
		// 组长已被回收（Cmd.Wait 返回），但其子孙（如 trap SIGTERM 的
		// vite/esbuild/node）可能仍存活于同一进程组。无条件向整组补发一次
		// SIGKILL 以确保不留下任何孤儿；若组已全灭，内核返回 ESRCH，无害。
		_ = syscall.Kill(-g.pgid, syscall.SIGKILL)
	case <-time.After(grace):
		// 组长自身超过 grace 仍未退出：直接对整组 SIGKILL。
		_ = syscall.Kill(-g.pgid, syscall.SIGKILL)
	}
}

func (g *unixGroup) Close() {}
