package system

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

type Preflight struct {
	problems []string
}

func NewPreflight() *Preflight {
	return &Preflight{}
}

func (p *Preflight) RequireCommand(name, hint string) {
	value := strings.TrimSpace(name)
	if value == "" {
		p.add("command name is not configured%s", formatHint(hint))
		return
	}
	if _, err := exec.LookPath(value); err != nil {
		p.add("required command %q not found%s", value, formatHint(hint))
	}
}

func (p *Preflight) RequireExecutable(pathOrName, label, hint string) {
	value := strings.TrimSpace(pathOrName)
	if value == "" {
		p.add("%s is not configured%s", label, formatHint(hint))
		return
	}
	if strings.ContainsRune(value, filepath.Separator) {
		info, err := os.Stat(value)
		if err != nil {
			p.add("%s not found at %s%s", label, value, formatHint(hint))
			return
		}
		if info.IsDir() || info.Mode()&0o111 == 0 {
			p.add("%s is not executable at %s%s", label, value, formatHint(hint))
		}
		return
	}
	if _, err := exec.LookPath(value); err != nil {
		p.add("missing %s %q%s", label, value, formatHint(hint))
	}
}

func (p *Preflight) RequireFile(path, label, hint string) {
	value := strings.TrimSpace(path)
	if value == "" {
		p.add("%s is not configured%s", label, formatHint(hint))
		return
	}
	info, err := os.Stat(value)
	if err != nil {
		p.add("%s not found at %s%s", label, value, formatHint(hint))
		return
	}
	if info.IsDir() {
		p.add("%s expected a file at %s%s", label, value, formatHint(hint))
	}
}

func (p *Preflight) RequireDir(path, label, hint string) {
	value := strings.TrimSpace(path)
	if value == "" {
		p.add("%s is not configured%s", label, formatHint(hint))
		return
	}
	info, err := os.Stat(value)
	if err != nil {
		p.add("%s not found at %s%s", label, value, formatHint(hint))
		return
	}
	if !info.IsDir() {
		p.add("%s expected a directory at %s%s", label, value, formatHint(hint))
	}
}

func (p *Preflight) Addf(format string, args ...any) {
	p.add(format, args...)
}

func (p *Preflight) Problems() []string {
	if len(p.problems) == 0 {
		return nil
	}
	out := make([]string, len(p.problems))
	copy(out, p.problems)
	return out
}

func (p *Preflight) Err(prefix string) error {
	if len(p.problems) == 0 {
		return nil
	}
	var builder strings.Builder
	builder.WriteString(strings.TrimSpace(prefix))
	for _, problem := range p.problems {
		builder.WriteString("\n- ")
		builder.WriteString(problem)
	}
	return errors.New(builder.String())
}

func (p *Preflight) add(format string, args ...any) {
	p.problems = append(p.problems, fmt.Sprintf(format, args...))
}

func formatHint(hint string) string {
	hint = strings.TrimSpace(hint)
	if hint == "" {
		return ""
	}
	return " (" + hint + ")"
}
