package cli

// Description: Structs to execute external commands.

import (
	"fmt"
	"io"
	"log"
	"os/exec"
	"strings"

	"gitlab.com/M0M097/pkg/internal/must"
)

// Executor holds the standard output and error writers for command execution.
// It can be used to run shell commands and log their output.
type Executor struct {
	// Stdout receives the command's standard output.
	Stdout io.Writer

	// Stderr receives the command's standard error.
	Stderr io.Writer
}

// Run executes a command in the shell and logs the command being run.
func (e *Executor) Run(fullCmd string) error {
	return Run(fullCmd, fullCmd, e.Stdout, e.Stderr)
}

// PanicOnFail uses Run to execute a command and panics if the command fails.
func (e *Executor) PanicOnFail(fullCmd string) {
	PanicOnFail(fullCmd, fullCmd, e.Stdout, e.Stderr)
}

// Pager returns a function that displays files using a pager command like "less".
//
// The pager command is determined by the "PAGER" environment variable, or the
// provided fallbackPager if "PAGER" is not set.
func Pager(exec *Executor, fallbackPager string) func(files ...string) {
	cmd := must.SetEnvIfNotSet("PAGER", fallbackPager) + " "
	return func(files ...string) { exec.PanicOnFail(cmd + strings.Join(files, " ")) }
}

// Run executes a command in the shell and optionally logs msg before the
// command runs. The output of the command is written to the provided stdout
// and stderr writers.
func Run(fullCmd, msg string, stdout, stderr io.Writer) error {
	if msg != "" {
		log.Println(msg)
	}
	split := strings.Split(fullCmd, " ")
	cmd := exec.Command(split[0], split[1:]...)
	cmd.Stdout, cmd.Stderr = stdout, stderr
	return cmd.Run()
}

// PanicOnFail runs a command and panics if the command fails.
func PanicOnFail(fullCmd, msg string, stdout, stderr io.Writer) {
	if err := Run(fullCmd, msg, stdout, stderr); err != nil {
		panic(fmt.Errorf("%s failed: %w", fullCmd, err))
	}
}
