package exitcode

import (
	"context"
	"errors"
)

// Code is the process exit code surfaced via ExitError.
//
// main.go maps these to os.Exit; values are stable across releases.
type Code int

const (
	OK        Code = 0
	Generic   Code = 1
	Cancelled Code = 2 // SIGINT/SIGTERM-triggered ctx cancellation
	Partial   Code = 3 // Run completed but some shards/tenants failed
)

// Error wraps a downstream error with a semantic Code.
//
// Commands return this from RunE when they want main.go to surface a specific
// exit code instead of the default Generic.
type Error struct {
	Code Code
	Err  error
}

func (e *Error) Error() string { return e.Err.Error() }
func (e *Error) Unwrap() error { return e.Err }

// PartialFailure wraps err with Partial.
//
// Use when a run completed but some shards failed (e.g. vmctl exit non-zero
// on a subset of shards).
func PartialFailure(err error) error {
	return &Error{Code: Partial, Err: err}
}

// CancelledFailure wraps err with Cancelled.
//
// Use when a run was aborted by context cancellation rather than failing.
func CancelledFailure(err error) error {
	return &Error{Code: Cancelled, Err: err}
}

// Classify maps an error returned by cobra's Execute to a process Code.
//
// SIGINT/SIGTERM cancellation propagates as context.Canceled; an explicit
// Error surfaces its declared code; anything else falls back to Generic.
func Classify(err error) Code {
	if err == nil {
		return OK
	}
	if errors.Is(err, context.Canceled) {
		return Cancelled
	}
	var ee *Error
	if errors.As(err, &ee) {
		return ee.Code
	}
	return Generic
}
