package ui

import (
	"errors"
	"fmt"
	"os"
)

type exitCoder interface {
	ExitCode() int
}

// transparentExit is satisfied by *runner.ChildExitError without importing it.
type transparentExit interface {
	error
	Transparent() bool
	ExitCode() int
	IsSignaled() bool
}

// scrubbedEnvReporter is optionally satisfied by a transparent exit error to
// surface how many environment variables the sandbox scrubbed from the failed
// run. Kept separate from transparentExit so older implementations still
// classify as transparent.
type scrubbedEnvReporter interface {
	ScrubbedEnvCount() int
}

type exitDecision struct {
	transparent bool
	notice      bool
	code        int
	message     string
}

// classifyExit is the pure decision behind ExitFromCommandError. The notice is
// suppressed for signal exits (the user initiated the interrupt) and in silent
// mode.
func classifyExit(err error) exitDecision {
	var te transparentExit
	if !errors.As(err, &te) || !te.Transparent() {
		return exitDecision{}
	}

	d := exitDecision{transparent: true, code: te.ExitCode()}
	if !te.IsSignaled() && verbosityLevel != VerbosityLevelSilent {
		d.notice = true
		d.message = "↳ pmg: " + te.Error()

		// Env scrubbing produces no sandbox violation and the child's own
		// error (e.g. a registry 401) does not point at the cause, so hint at
		// it here. Names are not printed; they are at info level via --debug.
		if sr, ok := te.(scrubbedEnvReporter); ok && sr.ScrubbedEnvCount() > 0 {
			d.message += fmt.Sprintf(
				"\n↳ pmg: sandbox scrubbed %d env var(s) (names via --debug, re-allow with --sandbox-allow env=NAME)",
				sr.ScrubbedEnvCount())
		}
	}
	return d
}

// ExitFromCommandError is the single exit point for package-manager commands. A
// child that exited on its own is mirrored transparently; everything else keeps
// the visible PMG error framing.
func ExitFromCommandError(err error) {
	if err == nil {
		return
	}

	if d := classifyExit(err); d.transparent {
		ClearStatus()
		if d.notice {
			fmt.Fprintln(os.Stderr, Colors.Dim(d.message))
		}
		os.Exit(d.code)
	}

	var ec exitCoder
	if errors.As(err, &ec) {
		ErrorExitWithCode(err, ec.ExitCode())
		return
	}

	ErrorExit(err)
}
