// Package log provides structured logging and error handling for Core applications.
//
// This file implements structured error types and combined log-and-return helpers
// that simplify common error handling patterns.

package log

import (
	"errors"
	"fmt"
)

// Err represents a structured error with operational context.
// It implements the error interface and supports unwrapping.
type Err struct {
	Op   string // Operation being performed (e.g., "user.Save")
	Msg  string // Human-readable message
	Err  error  // Underlying error (optional)
	Code string // Error code (optional, e.g., "VALIDATION_FAILED")
}

// Error implements the error interface.
func (e *Err) Error() string {
	var prefix string
	if e.Op != "" {
		prefix = e.Op + ": "
	}
	if e.Err != nil {
		if e.Code != "" {
			return fmt.Sprintf("%s%s [%s]: %v", prefix, e.Msg, e.Code, e.Err)
		}
		return fmt.Sprintf("%s%s: %v", prefix, e.Msg, e.Err)
	}
	if e.Code != "" {
		return fmt.Sprintf("%s%s [%s]", prefix, e.Msg, e.Code)
	}
	return fmt.Sprintf("%s%s", prefix, e.Msg)
}

// Unwrap returns the underlying error for use with errors.Is and errors.As.
func (e *Err) Unwrap() error {
	return e.Err
}

// --- Error Creation Functions ---

// E creates a new Err with operation context.
// The underlying error can be nil for creating errors without a cause.
//
// Example:
//
//	return log.E("user.Save", "failed to save user", err)
//	return log.E("api.Call", "rate limited", nil)  // No underlying cause
func E(op, msg string, err error) error {
	return &Err{Op: op, Msg: msg, Err: err}
}

// Wrap wraps an error with operation context.
// Returns nil if err is nil, to support conditional wrapping.
// Preserves error Code if the wrapped error is an *Err.
//
// Example:
//
//	return log.Wrap(err, "db.Query", "database query failed")
func Wrap(err error, op, msg string) error {
	if err == nil {
		return nil
	}
	// Preserve Code from wrapped *Err
	var logErr *Err
	if As(err, &logErr) && logErr.Code != "" {
		return &Err{Op: op, Msg: msg, Err: err, Code: logErr.Code}
	}
	return &Err{Op: op, Msg: msg, Err: err}
}

// WrapCode wraps an error with operation context and error code.
// Returns nil only if both err is nil AND code is empty.
// Useful for API errors that need machine-readable codes.
//
// Example:
//
//	return log.WrapCode(err, "VALIDATION_ERROR", "user.Validate", "invalid email")
func WrapCode(err error, code, op, msg string) error {
	if err == nil && code == "" {
		return nil
	}
	return &Err{Op: op, Msg: msg, Err: err, Code: code}
}

// NewCode creates an error with just code and message (no underlying error).
// Useful for creating sentinel errors with codes.
//
// Example:
//
//	var ErrNotFound = log.NewCode("NOT_FOUND", "resource not found")
func NewCode(code, msg string) error {
	return &Err{Msg: msg, Code: code}
}

// --- Standard Library Wrappers ---

// Is reports whether any error in err's tree matches target.
// Wrapper around errors.Is for convenience.
func Is(err, target error) bool {
	return errors.Is(err, target)
}

// As finds the first error in err's tree that matches target.
// Wrapper around errors.As for convenience.
func As(err error, target any) bool {
	return errors.As(err, target)
}

// NewError creates a simple error with the given text.
// Wrapper around errors.New for convenience.
func NewError(text string) error {
	return errors.New(text)
}

// Join combines multiple errors into one.
// Wrapper around errors.Join for convenience.
func Join(errs ...error) error {
	return errors.Join(errs...)
}

// --- Error Introspection Helpers ---

// Op extracts the operation name from an error.
// Returns empty string if the error is not an *Err.
func Op(err error) string {
	var e *Err
	if As(err, &e) {
		return e.Op
	}
	return ""
}

// ErrCode extracts the error code from an error.
// Returns empty string if the error is not an *Err or has no code.
func ErrCode(err error) string {
	var e *Err
	if As(err, &e) {
		return e.Code
	}
	return ""
}

// Message extracts the message from an error.
// Returns the error's Error() string if not an *Err.
func Message(err error) string {
	if err == nil {
		return ""
	}
	var e *Err
	if As(err, &e) {
		return e.Msg
	}
	return err.Error()
}

// Root returns the root cause of an error chain.
// Unwraps until no more wrapped errors are found.
func Root(err error) error {
	if err == nil {
		return nil
	}
	for {
		unwrapped := errors.Unwrap(err)
		if unwrapped == nil {
			return err
		}
		err = unwrapped
	}
}

// --- Combined Log-and-Return Helpers ---

// LogError logs an error at Error level and returns a wrapped error.
// Reduces boilerplate in error handling paths.
//
// Example:
//
//	// Before
//	if err != nil {
//	    log.Error("failed to save", "err", err)
//	    return errors.Wrap(err, "user.Save", "failed to save")
//	}
//
//	// After
//	if err != nil {
//	    return log.LogError(err, "user.Save", "failed to save")
//	}
func LogError(err error, op, msg string) error {
	if err == nil {
		return nil
	}
	wrapped := Wrap(err, op, msg)
	defaultLogger.Error(msg, "op", op, "err", err)
	return wrapped
}

// LogWarn logs at Warn level and returns a wrapped error.
// Use for recoverable errors that should be logged but not treated as critical.
//
// Example:
//
//	return log.LogWarn(err, "cache.Get", "cache miss, falling back to db")
func LogWarn(err error, op, msg string) error {
	if err == nil {
		return nil
	}
	wrapped := Wrap(err, op, msg)
	defaultLogger.Warn(msg, "op", op, "err", err)
	return wrapped
}

// Must panics if err is not nil, logging first.
// Use for errors that should never happen and indicate programmer error.
//
// Example:
//
//	log.Must(Initialize(), "app", "startup failed")
func Must(err error, op, msg string) {
	if err != nil {
		defaultLogger.Error(msg, "op", op, "err", err)
		panic(Wrap(err, op, msg))
	}
}
