package extensionflow

import (
	"errors"
	"fmt"
)

// InvocationErrorCode is the closed Agent-visible execution error set from the
// Extension Platform contract. Callers must classify by code rather than by
// provider text; InvocationError never exposes an upstream response, URL, or
// credential-bearing cause in Error().
type InvocationErrorCode string

const (
	InvocationValidation              InvocationErrorCode = "validation"
	InvocationPermissionDenied        InvocationErrorCode = "permission_denied"
	InvocationNotFound                InvocationErrorCode = "not_found"
	InvocationConflict                InvocationErrorCode = "conflict"
	InvocationAgentCatalogStale       InvocationErrorCode = "agent_extension_catalog_stale"
	InvocationProviderAuthUnavailable InvocationErrorCode = "provider_auth_unavailable"
	InvocationRateLimited             InvocationErrorCode = "rate_limited"
	InvocationUpstreamFailure         InvocationErrorCode = "upstream_failure"
	InvocationInternal                InvocationErrorCode = "internal"
)

// InvocationError carries a safe stable code/message and an optional internal
// cause for server observability. The cause is intentionally excluded from the
// rendered message so wrapping net/http errors cannot leak credential-bearing
// URLs or provider response text to the Agent.
type InvocationError struct {
	Code    InvocationErrorCode
	Message string
	cause   error
}

func (err *InvocationError) Error() string {
	if err == nil {
		return ""
	}
	if err.Message != "" {
		return err.Message
	}
	return string(err.Code)
}

func (err *InvocationError) Unwrap() error {
	if err == nil {
		return nil
	}
	return err.cause
}

func invocationError(code InvocationErrorCode, message string, cause error) error {
	return &InvocationError{Code: code, Message: message, cause: cause}
}

// InvocationErrorCodeOf extracts the stable execution code from err.
func InvocationErrorCodeOf(err error) (InvocationErrorCode, bool) {
	var invocationErr *InvocationError
	if !errors.As(err, &invocationErr) || invocationErr == nil {
		return "", false
	}
	return invocationErr.Code, true
}

// InvocationErrorCause returns the internal cause for logging/telemetry. API
// adapters should never serialize it.
func InvocationErrorCause(err error) error {
	var invocationErr *InvocationError
	if !errors.As(err, &invocationErr) || invocationErr == nil {
		return nil
	}
	return invocationErr.cause
}

func invocationInternal(message string, cause error) error {
	if cause == nil {
		cause = fmt.Errorf("%s", message)
	}
	return invocationError(InvocationInternal, message, cause)
}
