package filter

import (
	"fmt"
)

// ErrorCode categorizes parse errors for hint lookup.
type ErrorCode int

const (
	ErrorCodeUnknown ErrorCode = iota
	ErrorCodeUnexpectedToken
	ErrorCodeUnexpectedEOF
	ErrorCodeEmptyExpression
	ErrorCodeMissingClosingBracket
	ErrorCodeMissingClosingBrace
	ErrorCodeIllegalToken
	ErrorCodeMissingOperand
	ErrorCodeEmptyGitFilter
	ErrorCodeMissingGitRef
	ErrorCodeInvalidGlob
)

// ParseError represents an error that occurred during parsing.
type ParseError struct {
	// Title is a high-level error description (e.g., "Unclosed Git filter expression")
	Title string
	// Message is a detailed explanation shown at the problematic location
	// (e.g., "this Git-based expression is missing a closing ']'")
	Message string
	// Query is the original filter query
	Query string
	// TokenLiteral is the problematic token
	TokenLiteral string
	// TokenLength is the length of the problematic token (used for underline width)
	TokenLength int
	// Position is the position of the problematic token
	Position int
	// ErrorPosition is the position to show the caret (e.g. for unclosed brackets, it points to the opening bracket)
	ErrorPosition int
	// ErrorCode is the error code, used for hint lookup
	ErrorCode ErrorCode
}

// Error returns a string representation of the error.
//
// We suppress the gocritic "hugeParam" warning because this is a very large struct,
// but we need it to implement the error interface, not its pointer.
//
//nolint:gocritic
func (e ParseError) Error() string {
	return fmt.Sprintf("Parse error at position %d: %s", e.Position, e.Message)
}

// NewParseError creates a new ParseError with the given message and position.
func NewParseError(message string, position int) error {
	return ParseError{Message: message, Position: position}
}

// NewParseErrorWithContext creates a new ParseError with full context for rich diagnostics.
func NewParseErrorWithContext(
	title, message string,
	position, errorPosition int,
	query, tokenLiteral string,
	tokenLength int,
	code ErrorCode,
) error {
	return ParseError{
		Title:         title,
		Message:       message,
		Position:      position,
		ErrorPosition: errorPosition,
		Query:         query,
		TokenLiteral:  tokenLiteral,
		TokenLength:   tokenLength,
		ErrorCode:     code,
	}
}

// EvaluationError represents an error that occurred during evaluation.
type EvaluationError struct {
	Cause   error
	Message string
}

func (e EvaluationError) Error() string {
	if e.Cause != nil {
		return fmt.Sprintf("Evaluation error: %s: %v", e.Message, e.Cause)
	}

	return "evaluation error: " + e.Message
}

// NewEvaluationError creates a new EvaluationError with the given message.
func NewEvaluationError(message string) error {
	return EvaluationError{Message: message}
}

// NewEvaluationErrorWithCause creates a new EvaluationError with the given message and cause.
func NewEvaluationErrorWithCause(message string, cause error) error {
	return EvaluationError{Message: message, Cause: cause}
}

// FilterQueryRequiresDiscoveryError is an error that is returned when a filter query
// requires discovery of Terragrunt configurations.
type FilterQueryRequiresDiscoveryError struct {
	Query string
}

func (e FilterQueryRequiresDiscoveryError) Error() string {
	return fmt.Sprintf(
		"Filter query '%s' requires discovery of Terragrunt configurations, "+
			"which is not supported when evaluating filters on generic files",
		e.Query,
	)
}
