// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package interpreter

import (
	"fmt"
	"strings"

	"github.com/google/cel-go/common/containers"
	"github.com/google/cel-go/common/types"
	"github.com/google/cel-go/common/types/ref"
	"github.com/google/cel-go/common/types/traits"
)

// AttributeFactory provides methods creating Attribute and Qualifier values.
type AttributeFactory interface {
	// AbsoluteAttribute creates an attribute that refers to a top-level variable name.
	//
	// Checked expressions generate absolute attribute with a single name.
	// Parse-only expressions may have more than one possible absolute identifier when the
	// expression is created within a container, e.g. package or namespace.
	//
	// When there is more than one name supplied to the AbsoluteAttribute call, the names
	// must be in CEL's namespace resolution order. The name arguments provided here are
	// returned in the same order as they were provided by the NamespacedAttribute
	// CandidateVariableNames method.
	AbsoluteAttribute(id int64, names ...string) NamespacedAttribute

	// ConditionalAttribute creates an attribute with two Attribute branches, where the Attribute
	// that is resolved depends on the boolean evaluation of the input 'expr'.
	ConditionalAttribute(id int64, expr Interpretable, t, f Attribute) Attribute

	// MaybeAttribute creates an attribute that refers to either a field selection or a namespaced
	// variable name.
	//
	// Only expressions which have not been type-checked may generate oneof attributes.
	MaybeAttribute(id int64, name string) Attribute

	// RelativeAttribute creates an attribute whose value is a qualification of a dynamic
	// computation rather than a static variable reference.
	RelativeAttribute(id int64, operand Interpretable) Attribute

	// NewQualifier creates a qualifier on the target object with a given value.
	//
	// The 'val' may be an Attribute or any proto-supported map key type: bool, int, string, uint.
	//
	// The qualifier may consider the object type being qualified, if present. If absent, the
	// qualification should be considered dynamic and the qualification should still work, though
	// it may be sub-optimal.
	NewQualifier(objType *types.Type, qualID int64, val any, opt bool) (Qualifier, error)
}

// Qualifier marker interface for designating different qualifier values and where they appear
// within field selections and index call expressions (`_[_]`).
type Qualifier interface {
	// ID where the qualifier appears within an expression.
	ID() int64

	// IsOptional specifies whether the qualifier is optional.
	// Instead of a direct qualification, an optional qualifier will be resolved via QualifyIfPresent
	// rather than Qualify. A non-optional qualifier may also be resolved through QualifyIfPresent if
	// the object to qualify is itself optional.
	IsOptional() bool

	// Qualify performs a qualification, e.g. field selection, on the input object and returns
	// the value of the access and whether the value was set. A non-nil value with a false presence
	// test result indicates that the value being returned is the default value.
	Qualify(vars Activation, obj any) (any, error)

	// QualifyIfPresent qualifies the object if the qualifier is declared or defined on the object.
	// The 'presenceOnly' flag indicates that the value is not necessary, just a boolean status as
	// to whether the qualifier is present.
	QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error)
}

// ConstantQualifier interface embeds the Qualifier interface and provides an option to inspect the
// qualifier's constant value.
//
// Non-constant qualifiers are of Attribute type.
type ConstantQualifier interface {
	Qualifier

	// Value returns the constant value associated with the qualifier.
	Value() ref.Val
}

// Attribute values are a variable or value with an optional set of qualifiers, such as field, key,
// or index accesses.
type Attribute interface {
	Qualifier

	// AddQualifier adds a qualifier on the Attribute or error if the qualification is not a valid qualifier type.
	AddQualifier(Qualifier) (Attribute, error)

	// Resolve returns the value of the Attribute and whether it was present given an Activation.
	// For objects which support safe traversal, the value may be non-nil and the presence flag be false.
	//
	// If an error is encountered during attribute resolution, it will be returned immediately.
	// If the attribute cannot be resolved within the Activation, the result must be: `nil`, `error`
	// with the error indicating which variable was missing.
	Resolve(Activation) (any, error)
}

// NamespacedAttribute values are a variable within a namespace, and an optional set of qualifiers
// such as field, key, or index accesses.
type NamespacedAttribute interface {
	Attribute

	// CandidateVariableNames returns the possible namespaced variable names for this Attribute in
	// the CEL namespace resolution order.
	CandidateVariableNames() []string

	// Qualifiers returns the list of qualifiers associated with the Attribute.
	Qualifiers() []Qualifier
}

// AttrFactoryOption specifies a functional option for configuring an attribute factory.
type AttrFactoryOption func(*attrFactory) *attrFactory

// EnableErrorOnBadPresenceTest error generation when a presence test or optional field selection
// is performed on a primitive type.
func EnableErrorOnBadPresenceTest(value bool) AttrFactoryOption {
	return func(fac *attrFactory) *attrFactory {
		fac.errorOnBadPresenceTest = value
		return fac
	}
}

// NewAttributeFactory returns a default AttributeFactory which is produces Attribute values
// capable of resolving types by simple names and qualify the values using the supported qualifier
// types: bool, int, string, and uint.
func NewAttributeFactory(cont *containers.Container, a types.Adapter, p types.Provider, opts ...AttrFactoryOption) AttributeFactory {
	fac := &attrFactory{
		container: cont,
		adapter:   a,
		provider:  p,
	}
	for _, o := range opts {
		fac = o(fac)
	}
	return fac
}

type attrFactory struct {
	container *containers.Container
	adapter   types.Adapter
	provider  types.Provider

	errorOnBadPresenceTest bool
}

// AbsoluteAttribute refers to a variable value and an optional qualifier path.
//
// The namespaceNames represent the names the variable could have based on namespace
// resolution rules.
func (r *attrFactory) AbsoluteAttribute(id int64, names ...string) NamespacedAttribute {
	disambiguateNames := false
	for idx, name := range names {
		if strings.HasPrefix(name, ".") {
			disambiguateNames = true
			names[idx] = strings.TrimPrefix(name, ".")
		}
	}
	return &absoluteAttribute{
		id:                     id,
		namespaceNames:         names,
		disambiguateNames:      disambiguateNames,
		qualifiers:             []Qualifier{},
		adapter:                r.adapter,
		provider:               r.provider,
		fac:                    r,
		errorOnBadPresenceTest: r.errorOnBadPresenceTest,
	}
}

// ConditionalAttribute supports the case where an attribute selection may occur on a conditional
// expression, e.g. (cond ? a : b).c
func (r *attrFactory) ConditionalAttribute(id int64, expr Interpretable, t, f Attribute) Attribute {
	return &conditionalAttribute{
		id:      id,
		expr:    expr,
		truthy:  t,
		falsy:   f,
		adapter: r.adapter,
		fac:     r,
	}
}

// MaybeAttribute collects variants of unchecked AbsoluteAttribute values which could either be
// direct variable accesses or some combination of variable access with qualification.
func (r *attrFactory) MaybeAttribute(id int64, name string) Attribute {
	var names []string
	// When there's a single name with a dot prefix, it indicates that the 'maybe' attribute is a
	// globally namespaced identifier.
	if strings.HasPrefix(name, ".") {
		names = append(names, name)
	} else {
		// In all other cases, the candidate names should be inferred.
		names = r.container.ResolveCandidateNames(name)
	}
	return &maybeAttribute{
		id: id,
		attrs: []NamespacedAttribute{
			r.AbsoluteAttribute(id, names...),
		},
		adapter:  r.adapter,
		provider: r.provider,
		fac:      r,
	}
}

// RelativeAttribute refers to an expression and an optional qualifier path.
func (r *attrFactory) RelativeAttribute(id int64, operand Interpretable) Attribute {
	return &relativeAttribute{
		id:                     id,
		operand:                operand,
		qualifiers:             []Qualifier{},
		adapter:                r.adapter,
		fac:                    r,
		errorOnBadPresenceTest: r.errorOnBadPresenceTest,
	}
}

// NewQualifier is an implementation of the AttributeFactory interface.
func (r *attrFactory) NewQualifier(objType *types.Type, qualID int64, val any, opt bool) (Qualifier, error) {
	// Before creating a new qualifier check to see if this is a protobuf message field access.
	// If so, use the precomputed GetFrom qualification method rather than the standard
	// stringQualifier.
	str, isStr := val.(string)
	if isStr && objType != nil && objType.Kind() == types.StructKind {
		ft, found := r.provider.FindStructFieldType(objType.TypeName(), str)
		if found && ft.IsSet != nil && ft.GetFrom != nil {
			return &fieldQualifier{
				id:        qualID,
				Name:      str,
				FieldType: ft,
				adapter:   r.adapter,
				optional:  opt,
			}, nil
		}
	}
	return newQualifier(r.adapter, qualID, val, opt, r.errorOnBadPresenceTest)
}

type absoluteAttribute struct {
	id int64
	// namespaceNames represent the names the variable could have based on declared container
	// (package) of the expression.
	namespaceNames []string
	// disambiguateNames indicates whether the namespaceNames require disambiguation with local variables.
	disambiguateNames bool

	qualifiers []Qualifier
	adapter    types.Adapter
	provider   types.Provider
	fac        AttributeFactory

	errorOnBadPresenceTest bool
}

// ID implements the Attribute interface method.
func (a *absoluteAttribute) ID() int64 {
	qualCount := len(a.qualifiers)
	if qualCount == 0 {
		return a.id
	}
	return a.qualifiers[qualCount-1].ID()
}

// IsOptional returns trivially false for an attribute as the attribute represents a fully
// qualified variable name. If the attribute is used in an optional manner, then an attrQualifier
// is created and marks the attribute as optional.
func (a *absoluteAttribute) IsOptional() bool {
	return false
}

// AddQualifier implements the Attribute interface method.
func (a *absoluteAttribute) AddQualifier(qual Qualifier) (Attribute, error) {
	a.qualifiers = append(a.qualifiers, qual)
	return a, nil
}

// CandidateVariableNames implements the NamespaceAttribute interface method.
func (a *absoluteAttribute) CandidateVariableNames() []string {
	return a.namespaceNames
}

// Qualifiers returns the list of Qualifier instances associated with the namespaced attribute.
func (a *absoluteAttribute) Qualifiers() []Qualifier {
	return a.qualifiers
}

// Qualify is an implementation of the Qualifier interface method.
func (a *absoluteAttribute) Qualify(vars Activation, obj any) (any, error) {
	return attrQualify(a.fac, vars, obj, a)
}

// QualifyIfPresent is an implementation of the Qualifier interface method.
func (a *absoluteAttribute) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) {
	return attrQualifyIfPresent(a.fac, vars, obj, a, presenceOnly)
}

// String implements the Stringer interface method.
func (a *absoluteAttribute) String() string {
	return fmt.Sprintf("id: %v, names: %v", a.id, a.namespaceNames)
}

// Resolve returns the resolved Attribute value given the Activation, or error if the Attribute
// variable is not found, or if its Qualifiers cannot be applied successfully.
//
// If the variable name cannot be found as an Activation variable or in the TypeProvider as
// a type, then the result is `nil`, `error` with the error indicating the name of the first
// variable searched as missing.
func (a *absoluteAttribute) Resolve(vars Activation) (any, error) {
	// unwrap any local activations to ensure that we reach the variables provided as input
	// to the expression in the event that we need to disambiguate between global and local
	// variables.
	//
	// Presently, only dynamic and constant slot activations created during comprehensions
	// support 'unwrapping', which is consistent with how local variables are introduced into CEL.
	var inputVars Activation
	if a.disambiguateNames {
		inputVars = vars
		wrapped, ok := inputVars.(activationWrapper)
		for ok {
			inputVars = wrapped.Unwrap()
			wrapped, ok = inputVars.(activationWrapper)
		}
	}
	for _, nm := range a.namespaceNames {
		// If the variable is found, process it. Otherwise, wait until the checks to
		// determine whether the type is unknown before returning.
		v := vars
		if a.disambiguateNames {
			v = inputVars
		}
		obj, found := v.ResolveName(nm)
		if found {
			if celErr, ok := obj.(*types.Err); ok {
				return nil, celErr
			}
			obj, isOpt, err := applyQualifiers(v, obj, a.qualifiers)
			if err != nil {
				return nil, err
			}
			if isOpt {
				val := a.adapter.NativeToValue(obj)
				if types.IsUnknown(val) {
					return val, nil
				}
				return types.OptionalOf(val), nil
			}
			return obj, nil
		}
		// Attempt to resolve the qualified type name if the name is not a variable identifier.
		typ, found := a.provider.FindIdent(nm)
		if found {
			if len(a.qualifiers) == 0 {
				return typ, nil
			}
		}
	}
	var attrNames strings.Builder
	for i, nm := range a.namespaceNames {
		if i != 0 {
			attrNames.WriteString(", ")
		}
		attrNames.WriteString(nm)
	}
	return nil, missingAttribute(attrNames.String())
}

type conditionalAttribute struct {
	id      int64
	expr    Interpretable
	truthy  Attribute
	falsy   Attribute
	adapter types.Adapter
	fac     AttributeFactory
}

// ID is an implementation of the Attribute interface method.
func (a *conditionalAttribute) ID() int64 {
	// There's a field access after the conditional.
	if a.truthy.ID() == a.falsy.ID() {
		return a.truthy.ID()
	}
	// Otherwise return the conditional id as the consistent id being tracked.
	return a.id
}

// IsOptional returns trivially false for an attribute as the attribute represents a fully
// qualified variable name. If the attribute is used in an optional manner, then an attrQualifier
// is created and marks the attribute as optional.
func (a *conditionalAttribute) IsOptional() bool {
	return false
}

// AddQualifier appends the same qualifier to both sides of the conditional, in effect managing
// the qualification of alternate attributes.
func (a *conditionalAttribute) AddQualifier(qual Qualifier) (Attribute, error) {
	_, err := a.truthy.AddQualifier(qual)
	if err != nil {
		return nil, err
	}
	_, err = a.falsy.AddQualifier(qual)
	if err != nil {
		return nil, err
	}
	return a, nil
}

// Qualify is an implementation of the Qualifier interface method.
func (a *conditionalAttribute) Qualify(vars Activation, obj any) (any, error) {
	return attrQualify(a.fac, vars, obj, a)
}

// QualifyIfPresent is an implementation of the Qualifier interface method.
func (a *conditionalAttribute) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) {
	return attrQualifyIfPresent(a.fac, vars, obj, a, presenceOnly)
}

// Resolve evaluates the condition, and then resolves the truthy or falsy branch accordingly.
func (a *conditionalAttribute) Resolve(vars Activation) (any, error) {
	val := a.expr.Eval(vars)
	if val == types.True {
		return a.truthy.Resolve(vars)
	}
	if val == types.False {
		return a.falsy.Resolve(vars)
	}
	if types.IsUnknown(val) {
		return val, nil
	}
	return nil, types.MaybeNoSuchOverloadErr(val).(*types.Err)
}

// String is an implementation of the Stringer interface method.
func (a *conditionalAttribute) String() string {
	return fmt.Sprintf("id: %v, truthy attribute: %v, falsy attribute: %v", a.id, a.truthy, a.falsy)
}

type maybeAttribute struct {
	id       int64
	attrs    []NamespacedAttribute
	adapter  types.Adapter
	provider types.Provider
	fac      AttributeFactory
}

// ID is an implementation of the Attribute interface method.
func (a *maybeAttribute) ID() int64 {
	return a.attrs[0].ID()
}

// IsOptional returns trivially false for an attribute as the attribute represents a fully
// qualified variable name. If the attribute is used in an optional manner, then an attrQualifier
// is created and marks the attribute as optional.
func (a *maybeAttribute) IsOptional() bool {
	return false
}

// AddQualifier adds a qualifier to each possible attribute variant, and also creates
// a new namespaced variable from the qualified value.
//
// The algorithm for building the maybe attribute is as follows:
//
// 1. Create a maybe attribute from a simple identifier when it occurs in a parsed-only expression
//
//	mb = MaybeAttribute(<id>, "a")
//
// Initializing the maybe attribute creates an absolute attribute internally which includes the
// possible namespaced names of the attribute. In this example, let's assume we are in namespace
// 'ns', then the maybe is either one of the following variable names:
//
//	possible variables names -- ns.a, a
//
// 2. Adding a qualifier to the maybe means that the variable name could be a longer qualified
// name, or a field selection on one of the possible variable names produced earlier:
//
//	mb.AddQualifier("b")
//
//	possible variables names -- ns.a.b, a.b
//	possible field selection -- ns.a['b'], a['b']
//
// If none of the attributes within the maybe resolves a value, the result is an error.
func (a *maybeAttribute) AddQualifier(qual Qualifier) (Attribute, error) {
	str := ""
	isStr := false
	cq, isConst := qual.(ConstantQualifier)
	if isConst {
		str, isStr = cq.Value().Value().(string)
	}
	var augmentedNames []string
	// First add the qualifier to all existing attributes in the oneof.
	for _, attr := range a.attrs {
		if isStr && len(attr.Qualifiers()) == 0 {
			candidateVars := attr.CandidateVariableNames()
			augmentedNames = make([]string, len(candidateVars))
			for i, name := range candidateVars {
				augmentedNames[i] = fmt.Sprintf("%s.%s", name, str)
			}
		}
		_, err := attr.AddQualifier(qual)
		if err != nil {
			return nil, err
		}
	}
	// Next, ensure the most specific variable / type reference is searched first.
	if len(augmentedNames) != 0 {
		a.attrs = append([]NamespacedAttribute{a.fac.AbsoluteAttribute(qual.ID(), augmentedNames...)}, a.attrs...)
	}
	return a, nil
}

// Qualify is an implementation of the Qualifier interface method.
func (a *maybeAttribute) Qualify(vars Activation, obj any) (any, error) {
	return attrQualify(a.fac, vars, obj, a)
}

// QualifyIfPresent is an implementation of the Qualifier interface method.
func (a *maybeAttribute) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) {
	return attrQualifyIfPresent(a.fac, vars, obj, a, presenceOnly)
}

// Resolve follows the variable resolution rules to determine whether the attribute is a variable
// or a field selection.
func (a *maybeAttribute) Resolve(vars Activation) (any, error) {
	var maybeErr error
	for _, attr := range a.attrs {
		obj, err := attr.Resolve(vars)
		// Return an error if one is encountered.
		if err != nil {
			resErr, ok := err.(*resolutionError)
			if !ok {
				return nil, err
			}
			// If this was not a missing variable error, return it.
			if !resErr.isMissingAttribute() {
				return nil, err
			}
			// When the variable is missing in a maybe attribute we defer erroring.
			if maybeErr == nil {
				maybeErr = resErr
			}
			// Continue attempting to resolve possible variables.
			continue
		}
		return obj, nil
	}
	// Else, produce a no such attribute error.
	return nil, maybeErr
}

// String is an implementation of the Stringer interface method.
func (a *maybeAttribute) String() string {
	return fmt.Sprintf("id: %v, attributes: %v", a.id, a.attrs)
}

type relativeAttribute struct {
	id         int64
	operand    Interpretable
	qualifiers []Qualifier
	adapter    types.Adapter
	fac        AttributeFactory

	errorOnBadPresenceTest bool
}

// ID is an implementation of the Attribute interface method.
func (a *relativeAttribute) ID() int64 {
	qualCount := len(a.qualifiers)
	if qualCount == 0 {
		return a.id
	}
	return a.qualifiers[qualCount-1].ID()
}

// IsOptional returns trivially false for an attribute as the attribute represents a fully
// qualified variable name. If the attribute is used in an optional manner, then an attrQualifier
// is created and marks the attribute as optional.
func (a *relativeAttribute) IsOptional() bool {
	return false
}

// AddQualifier implements the Attribute interface method.
func (a *relativeAttribute) AddQualifier(qual Qualifier) (Attribute, error) {
	a.qualifiers = append(a.qualifiers, qual)
	return a, nil
}

// Qualify is an implementation of the Qualifier interface method.
func (a *relativeAttribute) Qualify(vars Activation, obj any) (any, error) {
	return attrQualify(a.fac, vars, obj, a)
}

// QualifyIfPresent is an implementation of the Qualifier interface method.
func (a *relativeAttribute) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) {
	return attrQualifyIfPresent(a.fac, vars, obj, a, presenceOnly)
}

// Resolve expression value and qualifier relative to the expression result.
func (a *relativeAttribute) Resolve(vars Activation) (any, error) {
	// First, evaluate the operand.
	v := a.operand.Eval(vars)
	if types.IsError(v) {
		return nil, v.(*types.Err)
	}
	if types.IsUnknown(v) {
		return v, nil
	}
	obj, isOpt, err := applyQualifiers(vars, v, a.qualifiers)
	if err != nil {
		return nil, err
	}
	if isOpt {
		val := a.adapter.NativeToValue(obj)
		if types.IsUnknown(val) {
			return val, nil
		}
		return types.OptionalOf(val), nil
	}
	return obj, nil
}

// String is an implementation of the Stringer interface method.
func (a *relativeAttribute) String() string {
	return fmt.Sprintf("id: %v, operand: %v", a.id, a.operand)
}

func newQualifier(adapter types.Adapter, id int64, v any, opt, errorOnBadPresenceTest bool) (Qualifier, error) {
	var qual Qualifier
	switch val := v.(type) {
	case Attribute:
		// Note, attributes are initially identified as non-optional since they represent a top-level
		// field access; however, when used as a relative qualifier, e.g. a[?b.c], then an attrQualifier
		// is created which intercepts the IsOptional check for the attribute in order to return the
		// correct result.
		return &attrQualifier{
			id:        id,
			Attribute: val,
			optional:  opt,
		}, nil
	case string:
		qual = &stringQualifier{
			id:                     id,
			value:                  val,
			celValue:               types.String(val),
			adapter:                adapter,
			optional:               opt,
			errorOnBadPresenceTest: errorOnBadPresenceTest,
		}
	case int:
		qual = &intQualifier{
			id:                     id,
			value:                  int64(val),
			celValue:               types.Int(val),
			adapter:                adapter,
			optional:               opt,
			errorOnBadPresenceTest: errorOnBadPresenceTest,
		}
	case int32:
		qual = &intQualifier{
			id:                     id,
			value:                  int64(val),
			celValue:               types.Int(val),
			adapter:                adapter,
			optional:               opt,
			errorOnBadPresenceTest: errorOnBadPresenceTest,
		}
	case int64:
		qual = &intQualifier{
			id:                     id,
			value:                  val,
			celValue:               types.Int(val),
			adapter:                adapter,
			optional:               opt,
			errorOnBadPresenceTest: errorOnBadPresenceTest,
		}
	case uint:
		qual = &uintQualifier{
			id:                     id,
			value:                  uint64(val),
			celValue:               types.Uint(val),
			adapter:                adapter,
			optional:               opt,
			errorOnBadPresenceTest: errorOnBadPresenceTest,
		}
	case uint32:
		qual = &uintQualifier{
			id:                     id,
			value:                  uint64(val),
			celValue:               types.Uint(val),
			adapter:                adapter,
			optional:               opt,
			errorOnBadPresenceTest: errorOnBadPresenceTest,
		}
	case uint64:
		qual = &uintQualifier{
			id:                     id,
			value:                  val,
			celValue:               types.Uint(val),
			adapter:                adapter,
			optional:               opt,
			errorOnBadPresenceTest: errorOnBadPresenceTest,
		}
	case bool:
		qual = &boolQualifier{
			id:                     id,
			value:                  val,
			celValue:               types.Bool(val),
			adapter:                adapter,
			optional:               opt,
			errorOnBadPresenceTest: errorOnBadPresenceTest,
		}
	case float32:
		qual = &doubleQualifier{
			id:                     id,
			value:                  float64(val),
			celValue:               types.Double(val),
			adapter:                adapter,
			optional:               opt,
			errorOnBadPresenceTest: errorOnBadPresenceTest,
		}
	case float64:
		qual = &doubleQualifier{
			id:                     id,
			value:                  val,
			celValue:               types.Double(val),
			adapter:                adapter,
			optional:               opt,
			errorOnBadPresenceTest: errorOnBadPresenceTest,
		}
	case types.String:
		qual = &stringQualifier{
			id:                     id,
			value:                  string(val),
			celValue:               val,
			adapter:                adapter,
			optional:               opt,
			errorOnBadPresenceTest: errorOnBadPresenceTest,
		}
	case types.Int:
		qual = &intQualifier{
			id:                     id,
			value:                  int64(val),
			celValue:               val,
			adapter:                adapter,
			optional:               opt,
			errorOnBadPresenceTest: errorOnBadPresenceTest,
		}
	case types.Uint:
		qual = &uintQualifier{
			id:                     id,
			value:                  uint64(val),
			celValue:               val,
			adapter:                adapter,
			optional:               opt,
			errorOnBadPresenceTest: errorOnBadPresenceTest,
		}
	case types.Bool:
		qual = &boolQualifier{
			id:                     id,
			value:                  bool(val),
			celValue:               val,
			adapter:                adapter,
			optional:               opt,
			errorOnBadPresenceTest: errorOnBadPresenceTest,
		}
	case types.Double:
		qual = &doubleQualifier{
			id:                     id,
			value:                  float64(val),
			celValue:               val,
			adapter:                adapter,
			optional:               opt,
			errorOnBadPresenceTest: errorOnBadPresenceTest,
		}
	case *types.Unknown:
		qual = &unknownQualifier{id: id, value: val}
	default:
		if q, ok := v.(Qualifier); ok {
			return q, nil
		}
		return nil, fmt.Errorf("invalid qualifier type: %T", v)
	}
	return qual, nil
}

type attrQualifier struct {
	id int64
	Attribute
	optional bool
}

// ID implements the Qualifier interface method and returns the qualification instruction id
// rather than the attribute id.
func (q *attrQualifier) ID() int64 {
	return q.id
}

// IsOptional implements the Qualifier interface method.
func (q *attrQualifier) IsOptional() bool {
	return q.optional
}

type stringQualifier struct {
	id                     int64
	value                  string
	celValue               ref.Val
	adapter                types.Adapter
	optional               bool
	errorOnBadPresenceTest bool
}

// ID is an implementation of the Qualifier interface method.
func (q *stringQualifier) ID() int64 {
	return q.id
}

// IsOptional implements the Qualifier interface method.
func (q *stringQualifier) IsOptional() bool {
	return q.optional
}

// Qualify implements the Qualifier interface method.
func (q *stringQualifier) Qualify(vars Activation, obj any) (any, error) {
	val, _, err := q.qualifyInternal(vars, obj, false, false)
	return val, err
}

// QualifyIfPresent is an implementation of the Qualifier interface method.
func (q *stringQualifier) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) {
	return q.qualifyInternal(vars, obj, true, presenceOnly)
}

func (q *stringQualifier) qualifyInternal(vars Activation, obj any, presenceTest, presenceOnly bool) (any, bool, error) {
	s := q.value
	switch o := obj.(type) {
	case map[string]any:
		obj, isKey := o[s]
		if isKey {
			return obj, true, nil
		}
	case map[string]string:
		obj, isKey := o[s]
		if isKey {
			return obj, true, nil
		}
	case map[string]int:
		obj, isKey := o[s]
		if isKey {
			return obj, true, nil
		}
	case map[string]int32:
		obj, isKey := o[s]
		if isKey {
			return obj, true, nil
		}
	case map[string]int64:
		obj, isKey := o[s]
		if isKey {
			return obj, true, nil
		}
	case map[string]uint:
		obj, isKey := o[s]
		if isKey {
			return obj, true, nil
		}
	case map[string]uint32:
		obj, isKey := o[s]
		if isKey {
			return obj, true, nil
		}
	case map[string]uint64:
		obj, isKey := o[s]
		if isKey {
			return obj, true, nil
		}
	case map[string]float32:
		obj, isKey := o[s]
		if isKey {
			return obj, true, nil
		}
	case map[string]float64:
		obj, isKey := o[s]
		if isKey {
			return obj, true, nil
		}
	case map[string]bool:
		obj, isKey := o[s]
		if isKey {
			return obj, true, nil
		}
	default:
		return refQualify(q.adapter, obj, q.celValue, presenceTest, presenceOnly, q.errorOnBadPresenceTest)
	}
	if presenceTest {
		return nil, false, nil
	}
	return nil, false, missingKey(q.celValue)
}

// Value implements the ConstantQualifier interface
func (q *stringQualifier) Value() ref.Val {
	return q.celValue
}

type intQualifier struct {
	id                     int64
	value                  int64
	celValue               ref.Val
	adapter                types.Adapter
	optional               bool
	errorOnBadPresenceTest bool
}

// ID is an implementation of the Qualifier interface method.
func (q *intQualifier) ID() int64 {
	return q.id
}

// IsOptional implements the Qualifier interface method.
func (q *intQualifier) IsOptional() bool {
	return q.optional
}

// Qualify implements the Qualifier interface method.
func (q *intQualifier) Qualify(vars Activation, obj any) (any, error) {
	val, _, err := q.qualifyInternal(vars, obj, false, false)
	return val, err
}

// QualifyIfPresent is an implementation of the Qualifier interface method.
func (q *intQualifier) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) {
	return q.qualifyInternal(vars, obj, true, presenceOnly)
}

func (q *intQualifier) qualifyInternal(vars Activation, obj any, presenceTest, presenceOnly bool) (any, bool, error) {
	i := q.value
	var isMap bool
	switch o := obj.(type) {
	// The specialized map types supported by an int qualifier are considerably fewer than the set
	// of specialized map types supported by string qualifiers since they are less frequently used
	// than string-based map keys. Additional specializations may be added in the future if
	// desired.
	case map[int]any:
		isMap = true
		obj, isKey := o[int(i)]
		if isKey {
			return obj, true, nil
		}
	case map[int32]any:
		isMap = true
		obj, isKey := o[int32(i)]
		if isKey {
			return obj, true, nil
		}
	case map[int64]any:
		isMap = true
		obj, isKey := o[i]
		if isKey {
			return obj, true, nil
		}
	case []any:
		isIndex := i >= 0 && i < int64(len(o))
		if isIndex {
			return o[i], true, nil
		}
	case []string:
		isIndex := i >= 0 && i < int64(len(o))
		if isIndex {
			return o[i], true, nil
		}
	case []int:
		isIndex := i >= 0 && i < int64(len(o))
		if isIndex {
			return o[i], true, nil
		}
	case []int32:
		isIndex := i >= 0 && i < int64(len(o))
		if isIndex {
			return o[i], true, nil
		}
	case []int64:
		isIndex := i >= 0 && i < int64(len(o))
		if isIndex {
			return o[i], true, nil
		}
	case []uint:
		isIndex := i >= 0 && i < int64(len(o))
		if isIndex {
			return o[i], true, nil
		}
	case []uint32:
		isIndex := i >= 0 && i < int64(len(o))
		if isIndex {
			return o[i], true, nil
		}
	case []uint64:
		isIndex := i >= 0 && i < int64(len(o))
		if isIndex {
			return o[i], true, nil
		}
	case []float32:
		isIndex := i >= 0 && i < int64(len(o))
		if isIndex {
			return o[i], true, nil
		}
	case []float64:
		isIndex := i >= 0 && i < int64(len(o))
		if isIndex {
			return o[i], true, nil
		}
	case []bool:
		isIndex := i >= 0 && i < int64(len(o))
		if isIndex {
			return o[i], true, nil
		}
	default:
		return refQualify(q.adapter, obj, q.celValue, presenceTest, presenceOnly, q.errorOnBadPresenceTest)
	}
	if presenceTest {
		return nil, false, nil
	}
	if isMap {
		return nil, false, missingKey(q.celValue)
	}
	return nil, false, missingIndex(q.celValue)
}

// Value implements the ConstantQualifier interface
func (q *intQualifier) Value() ref.Val {
	return q.celValue
}

type uintQualifier struct {
	id                     int64
	value                  uint64
	celValue               ref.Val
	adapter                types.Adapter
	optional               bool
	errorOnBadPresenceTest bool
}

// ID is an implementation of the Qualifier interface method.
func (q *uintQualifier) ID() int64 {
	return q.id
}

// IsOptional implements the Qualifier interface method.
func (q *uintQualifier) IsOptional() bool {
	return q.optional
}

// Qualify implements the Qualifier interface method.
func (q *uintQualifier) Qualify(vars Activation, obj any) (any, error) {
	val, _, err := q.qualifyInternal(vars, obj, false, false)
	return val, err
}

// QualifyIfPresent is an implementation of the Qualifier interface method.
func (q *uintQualifier) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) {
	return q.qualifyInternal(vars, obj, true, presenceOnly)
}

func (q *uintQualifier) qualifyInternal(vars Activation, obj any, presenceTest, presenceOnly bool) (any, bool, error) {
	u := q.value
	switch o := obj.(type) {
	// The specialized map types supported by a uint qualifier are considerably fewer than the set
	// of specialized map types supported by string qualifiers since they are less frequently used
	// than string-based map keys. Additional specializations may be added in the future if
	// desired.
	case map[uint]any:
		obj, isKey := o[uint(u)]
		if isKey {
			return obj, true, nil
		}
	case map[uint32]any:
		obj, isKey := o[uint32(u)]
		if isKey {
			return obj, true, nil
		}
	case map[uint64]any:
		obj, isKey := o[u]
		if isKey {
			return obj, true, nil
		}
	default:
		return refQualify(q.adapter, obj, q.celValue, presenceTest, presenceOnly, q.errorOnBadPresenceTest)
	}
	if presenceTest {
		return nil, false, nil
	}
	return nil, false, missingKey(q.celValue)
}

// Value implements the ConstantQualifier interface
func (q *uintQualifier) Value() ref.Val {
	return q.celValue
}

type boolQualifier struct {
	id                     int64
	value                  bool
	celValue               ref.Val
	adapter                types.Adapter
	optional               bool
	errorOnBadPresenceTest bool
}

// ID is an implementation of the Qualifier interface method.
func (q *boolQualifier) ID() int64 {
	return q.id
}

// IsOptional implements the Qualifier interface method.
func (q *boolQualifier) IsOptional() bool {
	return q.optional
}

// Qualify implements the Qualifier interface method.
func (q *boolQualifier) Qualify(vars Activation, obj any) (any, error) {
	val, _, err := q.qualifyInternal(vars, obj, false, false)
	return val, err
}

// QualifyIfPresent is an implementation of the Qualifier interface method.
func (q *boolQualifier) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) {
	return q.qualifyInternal(vars, obj, true, presenceOnly)
}

func (q *boolQualifier) qualifyInternal(vars Activation, obj any, presenceTest, presenceOnly bool) (any, bool, error) {
	b := q.value
	switch o := obj.(type) {
	case map[bool]any:
		obj, isKey := o[b]
		if isKey {
			return obj, true, nil
		}
	default:
		return refQualify(q.adapter, obj, q.celValue, presenceTest, presenceOnly, q.errorOnBadPresenceTest)
	}
	if presenceTest {
		return nil, false, nil
	}
	return nil, false, missingKey(q.celValue)
}

// Value implements the ConstantQualifier interface
func (q *boolQualifier) Value() ref.Val {
	return q.celValue
}

// fieldQualifier indicates that the qualification is a well-defined field with a known
// field type. When the field type is known this can be used to improve the speed and
// efficiency of field resolution.
type fieldQualifier struct {
	id        int64
	Name      string
	FieldType *types.FieldType
	adapter   types.Adapter
	optional  bool
}

// ID is an implementation of the Qualifier interface method.
func (q *fieldQualifier) ID() int64 {
	return q.id
}

// IsOptional implements the Qualifier interface method.
func (q *fieldQualifier) IsOptional() bool {
	return q.optional
}

// Qualify implements the Qualifier interface method.
func (q *fieldQualifier) Qualify(vars Activation, obj any) (any, error) {
	if rv, ok := obj.(ref.Val); ok {
		obj = rv.Value()
	}
	val, err := q.FieldType.GetFrom(obj)
	if err != nil {
		return nil, err
	}
	return val, nil
}

// QualifyIfPresent is an implementation of the Qualifier interface method.
func (q *fieldQualifier) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) {
	if rv, ok := obj.(ref.Val); ok {
		obj = rv.Value()
	}
	if !q.FieldType.IsSet(obj) {
		return nil, false, nil
	}
	if presenceOnly {
		return nil, true, nil
	}
	val, err := q.FieldType.GetFrom(obj)
	if err != nil {
		return nil, false, err
	}
	return val, true, nil
}

// Value implements the ConstantQualifier interface
func (q *fieldQualifier) Value() ref.Val {
	return types.String(q.Name)
}

// doubleQualifier qualifies a CEL object, map, or list using a double value.
//
// This qualifier is used for working with dynamic data like JSON or protobuf.Any where the value
// type may not be known ahead of time and may not conform to the standard types supported as valid
// protobuf map key types.
type doubleQualifier struct {
	id                     int64
	value                  float64
	celValue               ref.Val
	adapter                types.Adapter
	optional               bool
	errorOnBadPresenceTest bool
}

// ID is an implementation of the Qualifier interface method.
func (q *doubleQualifier) ID() int64 {
	return q.id
}

// IsOptional implements the Qualifier interface method.
func (q *doubleQualifier) IsOptional() bool {
	return q.optional
}

// Qualify implements the Qualifier interface method.
func (q *doubleQualifier) Qualify(vars Activation, obj any) (any, error) {
	val, _, err := q.qualifyInternal(vars, obj, false, false)
	return val, err
}

func (q *doubleQualifier) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) {
	return q.qualifyInternal(vars, obj, true, presenceOnly)
}

func (q *doubleQualifier) qualifyInternal(vars Activation, obj any, presenceTest, presenceOnly bool) (any, bool, error) {
	return refQualify(q.adapter, obj, q.celValue, presenceTest, presenceOnly, q.errorOnBadPresenceTest)
}

// Value implements the ConstantQualifier interface
func (q *doubleQualifier) Value() ref.Val {
	return q.celValue
}

// unknownQualifier is a simple qualifier which always returns a preconfigured set of unknown values
// for any value subject to qualification. This is consistent with CEL's unknown handling elsewhere.
type unknownQualifier struct {
	id    int64
	value *types.Unknown
}

// ID is an implementation of the Qualifier interface method.
func (q *unknownQualifier) ID() int64 {
	return q.id
}

// IsOptional returns trivially false as an the unknown value is always returned.
func (q *unknownQualifier) IsOptional() bool {
	return false
}

// Qualify returns the unknown value associated with this qualifier.
func (q *unknownQualifier) Qualify(vars Activation, obj any) (any, error) {
	return q.value, nil
}

// QualifyIfPresent is an implementation of the Qualifier interface method.
func (q *unknownQualifier) QualifyIfPresent(vars Activation, obj any, presenceOnly bool) (any, bool, error) {
	return q.value, true, nil
}

// Value implements the ConstantQualifier interface
func (q *unknownQualifier) Value() ref.Val {
	return q.value
}

func applyQualifiers(vars Activation, obj any, qualifiers []Qualifier) (any, bool, error) {
	optObj, isOpt := obj.(*types.Optional)
	if isOpt {
		if !optObj.HasValue() {
			return optObj, false, nil
		}
		obj = optObj.GetValue().Value()
	}

	var err error
	for _, qual := range qualifiers {
		var qualObj any
		isOpt = isOpt || qual.IsOptional()
		if isOpt {
			var present bool
			qualObj, present, err = qual.QualifyIfPresent(vars, obj, false)
			if err != nil {
				return nil, false, err
			}
			if !present {
				// We return optional none here with a presence of 'false' as the layers
				// above will attempt to call types.OptionalOf() on a present value if any
				// of the qualifiers is optional.
				return types.OptionalNone, false, nil
			}
		} else {
			qualObj, err = qual.Qualify(vars, obj)
			if err != nil {
				return nil, false, err
			}
		}
		obj = qualObj
	}
	return obj, isOpt, nil
}

// attrQualify performs a qualification using the result of an attribute evaluation.
func attrQualify(fac AttributeFactory, vars Activation, obj any, qualAttr Attribute) (any, error) {
	val, err := qualAttr.Resolve(vars)
	if err != nil {
		return nil, err
	}
	qual, err := fac.NewQualifier(nil, qualAttr.ID(), val, qualAttr.IsOptional())
	if err != nil {
		return nil, err
	}
	return qual.Qualify(vars, obj)
}

// attrQualifyIfPresent conditionally performs the qualification of the result of attribute is present
// on the target object.
func attrQualifyIfPresent(fac AttributeFactory, vars Activation, obj any, qualAttr Attribute,
	presenceOnly bool) (any, bool, error) {
	val, err := qualAttr.Resolve(vars)
	if err != nil {
		return nil, false, err
	}
	qual, err := fac.NewQualifier(nil, qualAttr.ID(), val, qualAttr.IsOptional())
	if err != nil {
		return nil, false, err
	}
	return qual.QualifyIfPresent(vars, obj, presenceOnly)
}

// refQualify attempts to convert the value to a CEL value and then uses reflection methods to try and
// apply the qualifier with the option to presence test field accesses before retrieving field values.
func refQualify(adapter types.Adapter, obj any, idx ref.Val, presenceTest, presenceOnly, errorOnBadPresenceTest bool) (ref.Val, bool, error) {
	celVal := adapter.NativeToValue(obj)
	switch v := celVal.(type) {
	case *types.Unknown:
		return v, true, nil
	case *types.Err:
		return nil, false, v
	case traits.Mapper:
		val, found := v.Find(idx)
		// If the index is of the wrong type for the map, then it is possible
		// for the Find call to produce an error.
		if types.IsError(val) {
			return nil, false, val.(*types.Err)
		}
		if found {
			return val, true, nil
		}
		if presenceTest {
			return nil, false, nil
		}
		return nil, false, missingKey(idx)
	case traits.Lister:
		// If the index argument is not a valid numeric type, then it is possible
		// for the index operation to produce an error.
		i, err := types.IndexOrError(idx)
		if err != nil {
			return nil, false, err
		}
		celIndex := types.Int(i)
		if i >= 0 && celIndex < v.Size().(types.Int) {
			return v.Get(idx), true, nil
		}
		if presenceTest {
			return nil, false, nil
		}
		return nil, false, missingIndex(idx)
	case traits.Indexer:
		if presenceTest {
			ft, ok := v.(traits.FieldTester)
			if ok {
				presence := ft.IsSet(idx)
				if types.IsError(presence) {
					return nil, false, presence.(*types.Err)
				}
				// If not found or presence only test, then return.
				// Otherwise, if found, obtain the value later on.
				if presenceOnly || presence == types.False {
					return nil, presence == types.True, nil
				}
			}
		}
		val := v.Get(idx)
		if types.IsError(val) {
			return nil, false, val.(*types.Err)
		}
		return val, true, nil
	default:
		if presenceTest && !errorOnBadPresenceTest {
			return nil, false, nil
		}
		return nil, false, missingKey(idx)
	}
}

// resolutionError is a custom error type which encodes the different error states which may
// occur during attribute resolution.
type resolutionError struct {
	missingAttribute string
	missingIndex     ref.Val
	missingKey       ref.Val
}

func (e *resolutionError) isMissingAttribute() bool {
	return e.missingAttribute != ""
}

func missingIndex(missing ref.Val) *resolutionError {
	return &resolutionError{
		missingIndex: missing,
	}
}

func missingKey(missing ref.Val) *resolutionError {
	return &resolutionError{
		missingKey: missing,
	}
}

func missingAttribute(attr string) *resolutionError {
	return &resolutionError{
		missingAttribute: attr,
	}
}

// Error implements the error interface method.
func (e *resolutionError) Error() string {
	if e.missingKey != nil {
		return fmt.Sprintf("no such key: %v", e.missingKey)
	}
	if e.missingIndex != nil {
		return fmt.Sprintf("index out of bounds: %v", e.missingIndex)
	}
	if e.missingAttribute != "" {
		return fmt.Sprintf("no such attribute(s): %s", e.missingAttribute)
	}
	return "invalid attribute"
}

// Is implements the errors.Is() method used by more recent versions of Go.
func (e *resolutionError) Is(err error) bool {
	return err.Error() == e.Error()
}
