// 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 (
	"errors"
	"fmt"

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

	exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
)

// 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 *exprpb.Type, qualID int64, val interface{}) (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

	// Qualify performs a qualification, e.g. field selection, on the input object and returns
	// the value or error that results.
	Qualify(vars Activation, obj interface{}) (interface{}, error)
}

// 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 given the current Activation.
	Resolve(Activation) (interface{}, 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

	// HasQualifiers indicates whether the attribute has any qualifiers. An attribute without
	// qualifiers is just a variable.
	HasQualifiers() bool

	// TryResolve attempts to return the value of the attribute given the current Activation.
	// 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`,
	// `false`, `nil`.
	TryResolve(Activation) (interface{}, bool, error)
}

// 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(pkg packages.Packager,
	a ref.TypeAdapter,
	p ref.TypeProvider) AttributeFactory {
	return &attrFactory{
		pkg:      pkg,
		adapter:  a,
		provider: p,
	}
}

type attrFactory struct {
	pkg      packages.Packager
	adapter  ref.TypeAdapter
	provider ref.TypeProvider
}

// 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 {
	return &absoluteAttribute{
		id:             id,
		namespaceNames: names,
		qualifiers:     []Qualifier{},
		adapter:        r.adapter,
		provider:       r.provider,
		fac:            r,
	}
}

// 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 {
	return &maybeAttribute{
		id: id,
		attrs: []NamespacedAttribute{
			r.AbsoluteAttribute(id, r.pkg.ResolveCandidateNames(name)...),
		},
		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,
	}
}

// NewQualifier is an implementation of the AttributeFactory interface.
func (r *attrFactory) NewQualifier(objType *exprpb.Type,
	qualID int64,
	val interface{}) (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.GetMessageType() != "" {
		ft, found := r.provider.FindFieldType(objType.GetMessageType(), str)
		if found && ft.IsSet != nil && ft.GetFrom != nil {
			return &fieldQualifier{
				id:        qualID,
				Name:      str,
				FieldType: ft,
				adapter:   r.adapter,
			}, nil
		}
	}
	return newQualifier(r.adapter, qualID, val)
}

type absoluteAttribute struct {
	id int64
	// namespaceNames represent the names the variable could have based on declared container
	// (package) of the expression.
	namespaceNames []string
	qualifiers     []Qualifier
	adapter        ref.TypeAdapter
	provider       ref.TypeProvider
	fac            AttributeFactory
}

// ID implements the Attribute interface method.
func (a *absoluteAttribute) ID() int64 {
	return a.id
}

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

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

// HasQualifiers implements the NamespaceAttribute interface method.
func (a *absoluteAttribute) HasQualifiers() bool {
	return len(a.qualifiers) != 0
}

// CandidateVariableNames implements the NamespaceAttribute interface method.
func (a *absoluteAttribute) CandidateVariableNames() []string {
	return 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.
func (a *absoluteAttribute) Resolve(vars Activation) (interface{}, error) {
	obj, found, err := a.TryResolve(vars)
	if err != nil {
		return nil, err
	}
	if found {
		return obj, nil
	}
	return nil, fmt.Errorf("no such attribute: %v", a)
}

// TryResolve iterates through the namespaced variable names until one is found within the
// Activation or TypeProvider.
//
// If the variable name cannot be found as an Activation variable or in the TypeProvider as
// a type, then the result is `nil`, `false`, `nil` per the interface requirement.
func (a *absoluteAttribute) TryResolve(vars Activation) (interface{}, bool, error) {
	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.
		op, found := vars.ResolveName(nm)
		if found {
			var err error
			for _, qual := range a.qualifiers {
				op, err = qual.Qualify(vars, op)
				if err != nil {
					return nil, true, err
				}
			}
			return op, true, 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, true, nil
			}
			return nil, true, fmt.Errorf("no such attribute: %v", typ)
		}
	}
	return nil, false, nil
}

// Qualify is an implementation of the Qualifier interface method.
func (a *absoluteAttribute) Qualify(vars Activation, obj interface{}) (interface{}, error) {
	val, err := a.Resolve(vars)
	if err != nil {
		return nil, err
	}
	unk, isUnk := val.(types.Unknown)
	if isUnk {
		return unk, nil
	}
	qual, err := a.fac.NewQualifier(nil, a.id, val)
	if err != nil {
		return nil, err
	}
	return qual.Qualify(vars, obj)
}

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

// ID is an implementation of the Attribute interface method.
func (a *conditionalAttribute) ID() int64 {
	return a.id
}

// 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)
}

// 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
}

// Resolve evaluates the condition, and then resolves the truthy or falsy branch accordingly.
func (a *conditionalAttribute) Resolve(vars Activation) (interface{}, error) {
	val := a.expr.Eval(vars)
	if types.IsError(val) {
		return nil, val.Value().(error)
	}
	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.ValOrErr(val, "no such overload").Value().(error)
}

// Qualify is an implementation of the Qualifier interface method.
func (a *conditionalAttribute) Qualify(vars Activation, obj interface{}) (interface{}, error) {
	val, err := a.Resolve(vars)
	if err != nil {
		return nil, err
	}
	unk, isUnk := val.(types.Unknown)
	if isUnk {
		return unk, nil
	}
	qual, err := a.fac.NewQualifier(nil, a.id, val)
	if err != nil {
		return nil, err
	}
	return qual.Qualify(vars, obj)
}

type maybeAttribute struct {
	id       int64
	attrs    []NamespacedAttribute
	adapter  ref.TypeAdapter
	provider ref.TypeProvider
	fac      AttributeFactory
}

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

// 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)
}

// 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 := qual.(*stringQualifier)
	var augmentedNames []string
	// First add the qualifier to all existing attributes in the oneof.
	for _, attr := range a.attrs {
		if isStr && !attr.HasQualifiers() {
			candidateVars := attr.CandidateVariableNames()
			augmentedNames = make([]string,
				len(candidateVars),
				len(candidateVars))
			for i, name := range candidateVars {
				augmentedNames[i] = fmt.Sprintf("%s.%s", name, str.value)
			}
		}
		attr.AddQualifier(qual)
	}
	// Next, ensure the most specific variable / type reference is searched first.
	a.attrs = append([]NamespacedAttribute{
		a.fac.AbsoluteAttribute(qual.ID(), augmentedNames...),
	}, a.attrs...)
	return a, nil
}

// Resolve follows the variable resolution rules to determine whether the attribute is a variable
// or a field selection.
func (a *maybeAttribute) Resolve(vars Activation) (interface{}, error) {
	for _, attr := range a.attrs {
		obj, found, err := attr.TryResolve(vars)
		// Return an error if one is encountered.
		if err != nil {
			return nil, err
		}
		// If the object was found, return it.
		if found {
			return obj, nil
		}
	}
	// Else, produce a no such attribute error.
	return nil, fmt.Errorf("no such attribute: %v", a)
}

// Qualify is an implementation of the Qualifier interface method.
func (a *maybeAttribute) Qualify(vars Activation, obj interface{}) (interface{}, error) {
	val, err := a.Resolve(vars)
	if err != nil {
		return nil, err
	}
	unk, isUnk := val.(types.Unknown)
	if isUnk {
		return unk, nil
	}
	qual, err := a.fac.NewQualifier(nil, a.id, val)
	if err != nil {
		return nil, err
	}
	return qual.Qualify(vars, obj)
}

type relativeAttribute struct {
	id         int64
	operand    Interpretable
	qualifiers []Qualifier
	adapter    ref.TypeAdapter
	fac        AttributeFactory
}

// ID is an implementation of the Attribute interface method.
func (a *relativeAttribute) ID() int64 {
	return a.id
}

// 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)
}

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

// Resolve expression value and qualifier relative to the expression result.
func (a *relativeAttribute) Resolve(vars Activation) (interface{}, error) {
	// First, evaluate the operand.
	v := a.operand.Eval(vars)
	if types.IsError(v) {
		return nil, v.Value().(error)
	}
	if types.IsUnknown(v) {
		return v, nil
	}
	// Next, qualify it. Qualification handles unkonwns as well, so there's no need to recheck.
	var err error
	var obj interface{} = v
	for _, qual := range a.qualifiers {
		obj, err = qual.Qualify(vars, obj)
		if err != nil {
			return nil, err
		}
	}
	return obj, nil
}

// Qualify is an implementation of the Qualifier interface method.
func (a *relativeAttribute) Qualify(vars Activation, obj interface{}) (interface{}, error) {
	val, err := a.Resolve(vars)
	if err != nil {
		return nil, err
	}
	unk, isUnk := val.(types.Unknown)
	if isUnk {
		return unk, nil
	}
	qual, err := a.fac.NewQualifier(nil, a.id, val)
	if err != nil {
		return nil, err
	}
	return qual.Qualify(vars, obj)
}

func newQualifier(adapter ref.TypeAdapter, id int64, v interface{}) (Qualifier, error) {
	var qual Qualifier
	switch val := v.(type) {
	case Attribute:
		return val, nil
	case string:
		qual = &stringQualifier{id: id, value: val, celValue: types.String(val), adapter: adapter}
	case int:
		qual = &intQualifier{id: id, value: int64(val), celValue: types.Int(val), adapter: adapter}
	case int32:
		qual = &intQualifier{id: id, value: int64(val), celValue: types.Int(val), adapter: adapter}
	case int64:
		qual = &intQualifier{id: id, value: val, celValue: types.Int(val), adapter: adapter}
	case uint:
		qual = &uintQualifier{id: id, value: uint64(val), celValue: types.Uint(val), adapter: adapter}
	case uint32:
		qual = &uintQualifier{id: id, value: uint64(val), celValue: types.Uint(val), adapter: adapter}
	case uint64:
		qual = &uintQualifier{id: id, value: val, celValue: types.Uint(val), adapter: adapter}
	case bool:
		qual = &boolQualifier{id: id, value: val, celValue: types.Bool(val), adapter: adapter}
	case types.String:
		qual = &stringQualifier{id: id, value: string(val), celValue: val, adapter: adapter}
	case types.Int:
		qual = &intQualifier{id: id, value: int64(val), celValue: val, adapter: adapter}
	case types.Uint:
		qual = &uintQualifier{id: id, value: uint64(val), celValue: val, adapter: adapter}
	case types.Bool:
		qual = &boolQualifier{id: id, value: bool(val), celValue: val, adapter: adapter}
	default:
		return nil, fmt.Errorf("invalid qualifier type: %T", v)
	}
	return qual, nil
}

type stringQualifier struct {
	id       int64
	value    string
	celValue ref.Val
	adapter  ref.TypeAdapter
}

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

// Qualify implements the Qualifier interface method.
func (q *stringQualifier) Qualify(vars Activation, obj interface{}) (interface{}, error) {
	s := q.value
	isMap := false
	isKey := false
	switch o := obj.(type) {
	case map[string]interface{}:
		isMap = true
		obj, isKey = o[s]
	case map[string]string:
		isMap = true
		obj, isKey = o[s]
	case map[string]int:
		isMap = true
		obj, isKey = o[s]
	case map[string]int32:
		isMap = true
		obj, isKey = o[s]
	case map[string]int64:
		isMap = true
		obj, isKey = o[s]
	case map[string]uint:
		isMap = true
		obj, isKey = o[s]
	case map[string]uint32:
		isMap = true
		obj, isKey = o[s]
	case map[string]uint64:
		isMap = true
		obj, isKey = o[s]
	case map[string]float32:
		isMap = true
		obj, isKey = o[s]
	case map[string]float64:
		isMap = true
		obj, isKey = o[s]
	case map[string]bool:
		isMap = true
		obj, isKey = o[s]
	case types.Unknown:
		return o, nil
	default:
		elem, err := refResolve(q.adapter, q.celValue, obj)
		if err != nil {
			return nil, err
		}
		if types.IsUnknown(elem) {
			return elem, nil
		}
		return elem, nil
	}
	if isMap && !isKey {
		return nil, fmt.Errorf("no such key: %v", s)
	}
	return obj, nil
}

type intQualifier struct {
	id       int64
	value    int64
	celValue ref.Val
	adapter  ref.TypeAdapter
}

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

// Qualify implements the Qualifier interface method.
func (q *intQualifier) Qualify(vars Activation, obj interface{}) (interface{}, error) {
	i := q.value
	isMap := false
	isKey := false
	isIndex := false
	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]interface{}:
		isMap = true
		obj, isKey = o[int(i)]
	case map[int32]interface{}:
		isMap = true
		obj, isKey = o[int32(i)]
	case map[int64]interface{}:
		isMap = true
		obj, isKey = o[i]
	case []interface{}:
		isIndex = i >= 0 && i < int64(len(o))
		if isIndex {
			obj = o[i]
		}
	case []string:
		isIndex = i >= 0 && i < int64(len(o))
		if isIndex {
			obj = o[i]
		}
	case []int:
		isIndex = i >= 0 && i < int64(len(o))
		if isIndex {
			obj = o[i]
		}
	case []int32:
		isIndex = i >= 0 && i < int64(len(o))
		if isIndex {
			obj = o[i]
		}
	case []int64:
		isIndex = i >= 0 && i < int64(len(o))
		if isIndex {
			obj = o[i]
		}
	case []uint:
		isIndex = i >= 0 && i < int64(len(o))
		if isIndex {
			obj = o[i]
		}
	case []uint32:
		isIndex = i >= 0 && i < int64(len(o))
		if isIndex {
			obj = o[i]
		}
	case []uint64:
		isIndex = i >= 0 && i < int64(len(o))
		if isIndex {
			obj = o[i]
		}
	case []float32:
		isIndex = i >= 0 && i < int64(len(o))
		if isIndex {
			obj = o[i]
		}
	case []float64:
		isIndex = i >= 0 && i < int64(len(o))
		if isIndex {
			obj = o[i]
		}
	case []bool:
		isIndex = i >= 0 && i < int64(len(o))
		if isIndex {
			obj = o[i]
		}
	case types.Unknown:
		return o, nil
	default:
		elem, err := refResolve(q.adapter, q.celValue, obj)
		if err != nil {
			return nil, err
		}
		if types.IsUnknown(elem) {
			return elem, nil
		}
		return elem, nil
	}
	if isMap && !isKey {
		return nil, fmt.Errorf("no such key: %v", i)
	}
	if !isMap && !isIndex {
		return nil, fmt.Errorf("index out of bounds: %v", i)
	}
	return obj, nil
}

type uintQualifier struct {
	id       int64
	value    uint64
	celValue ref.Val
	adapter  ref.TypeAdapter
}

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

// Qualify implements the Qualifier interface method.
func (q *uintQualifier) Qualify(vars Activation, obj interface{}) (interface{}, error) {
	u := q.value
	isMap := false
	isKey := false
	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]interface{}:
		isMap = true
		obj, isKey = o[uint(u)]
	case map[uint32]interface{}:
		isMap = true
		obj, isKey = o[uint32(u)]
	case map[uint64]interface{}:
		isMap = true
		obj, isKey = o[u]
	case types.Unknown:
		return o, nil
	default:
		elem, err := refResolve(q.adapter, q.celValue, obj)
		if err != nil {
			return nil, err
		}
		if types.IsUnknown(elem) {
			return elem, nil
		}
		return elem, nil
	}
	if isMap && !isKey {
		return nil, fmt.Errorf("no such key: %v", u)
	}
	return obj, nil
}

type boolQualifier struct {
	id       int64
	value    bool
	celValue ref.Val
	adapter  ref.TypeAdapter
}

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

// Qualify implements the Qualifier interface method.
func (q *boolQualifier) Qualify(vars Activation, obj interface{}) (interface{}, error) {
	b := q.value
	isKey := false
	switch o := obj.(type) {
	// The specialized map types supported by a bool 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[bool]interface{}:
		obj, isKey = o[b]
	case types.Unknown:
		return o, nil
	default:
		elem, err := refResolve(q.adapter, q.celValue, obj)
		if err != nil {
			return nil, err
		}
		if types.IsUnknown(elem) {
			return elem, nil
		}
		return elem, nil
	}
	if !isKey {
		return nil, fmt.Errorf("no such key: %v", b)
	}
	return obj, nil
}

// 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 *ref.FieldType
	adapter   ref.TypeAdapter
}

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

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

// refResolve attempts to convert the value to a CEL value and then uses reflection methods
// to try and resolve the qualifier.
func refResolve(adapter ref.TypeAdapter, idx ref.Val, obj interface{}) (ref.Val, error) {
	celVal := adapter.NativeToValue(obj)
	mapper, isMapper := celVal.(traits.Mapper)
	if isMapper {
		elem, found := mapper.Find(idx)
		if !found {
			return nil, fmt.Errorf("no such key: %v", idx)
		}
		if types.IsError(elem) {
			return nil, elem.Value().(error)
		}
		return elem, nil
	}
	indexer, isIndexer := celVal.(traits.Indexer)
	if isIndexer {
		elem := indexer.Get(idx)
		if types.IsError(elem) {
			return nil, elem.Value().(error)
		}
		return elem, nil
	}
	if types.IsUnknown(celVal) {
		return celVal, nil
	}
	// TODO: If the types.Err value contains more than just an error message at some point in the
	// future, then it would be reasonable to return error values as ref.Val types rather than
	// simple go error types.
	if types.IsError(celVal) {
		return nil, celVal.Value().(error)
	}
	return nil, errors.New("no such overload")
}
