package checker

import (
	"fmt"
	"reflect"
	"regexp"
	"time"

	"github.com/expr-lang/expr/ast"
	"github.com/expr-lang/expr/builtin"
	. "github.com/expr-lang/expr/checker/nature"
	"github.com/expr-lang/expr/conf"
	"github.com/expr-lang/expr/file"
	"github.com/expr-lang/expr/parser"
)

var (
	anyType       = reflect.TypeOf(new(any)).Elem()
	boolType      = reflect.TypeOf(true)
	intType       = reflect.TypeOf(0)
	floatType     = reflect.TypeOf(float64(0))
	stringType    = reflect.TypeOf("")
	arrayType     = reflect.TypeOf([]any{})
	mapType       = reflect.TypeOf(map[string]any{})
	timeType      = reflect.TypeOf(time.Time{})
	durationType  = reflect.TypeOf(time.Duration(0))
	byteSliceType = reflect.TypeOf([]byte(nil))

	anyTypeSlice = []reflect.Type{anyType}
)

// ParseCheck parses input expression and checks its types. Also, it applies
// all provided patchers. In case of error, it returns error with a tree.
func ParseCheck(input string, config *conf.Config) (*parser.Tree, error) {
	tree, err := parser.ParseWithConfig(input, config)
	if err != nil {
		return tree, err
	}

	_, err = new(Checker).PatchAndCheck(tree, config)
	if err != nil {
		return tree, err
	}

	return tree, nil
}

// Check calls Check on a disposable Checker.
func Check(tree *parser.Tree, config *conf.Config) (reflect.Type, error) {
	return new(Checker).Check(tree, config)
}

type Checker struct {
	config          *conf.Config
	predicateScopes []predicateScope
	varScopes       []varScope
	err             *file.Error
	needsReset      bool
}

type predicateScope struct {
	collection Nature
	vars       []varScope
}

type varScope struct {
	name   string
	nature Nature
}

// PatchAndCheck applies all patchers and checks the tree.
func (v *Checker) PatchAndCheck(tree *parser.Tree, config *conf.Config) (reflect.Type, error) {
	v.reset(config)
	if len(config.Visitors) > 0 {
		// Run all patchers that dont support being run repeatedly first
		v.runVisitors(tree, false)

		// Run patchers that require multiple passes next (currently only Operator patching)
		v.runVisitors(tree, true)
	}
	return v.Check(tree, config)
}

// Check checks types of the expression tree. It returns type of the expression
// and error if any. If config is nil, then default configuration will be used.
func (v *Checker) Check(tree *parser.Tree, config *conf.Config) (reflect.Type, error) {
	v.reset(config)
	return v.check(tree)
}

// Run visitors in a given config over the given tree
// runRepeatable controls whether to filter for only vistors that require multiple passes or not
func (v *Checker) runVisitors(tree *parser.Tree, runRepeatable bool) {
	for {
		more := false
		for _, visitor := range v.config.Visitors {
			// We need to perform types check, because some visitors may rely on
			// types information available in the tree.
			_, _ = v.Check(tree, v.config)

			r, repeatable := visitor.(interface {
				Reset()
				ShouldRepeat() bool
			})

			if repeatable {
				if runRepeatable {
					r.Reset()
					ast.Walk(&tree.Node, visitor)
					more = more || r.ShouldRepeat()
				}
			} else {
				if !runRepeatable {
					ast.Walk(&tree.Node, visitor)
				}
			}
		}

		if !more {
			break
		}
	}
}

func (v *Checker) check(tree *parser.Tree) (reflect.Type, error) {
	nt := v.visit(tree.Node)

	// To keep compatibility with previous versions, we should return any, if nature is unknown.
	t := nt.Type
	if t == nil {
		t = anyType
	}

	if v.err != nil {
		return t, v.err.Bind(tree.Source)
	}

	if v.config.Expect != reflect.Invalid {
		if v.config.ExpectAny {
			if nt.IsUnknown(&v.config.NtCache) {
				return t, nil
			}
		}

		switch v.config.Expect {
		case reflect.Int, reflect.Int64, reflect.Float64:
			if !nt.IsNumber() {
				return nil, fmt.Errorf("expected %v, but got %s", v.config.Expect, nt.String())
			}
		default:
----
214:	case *ast.BuiltinNode:
222:	case *ast.SequenceNode:
224:	case *ast.ConditionalNode:
695:func (v *Checker) builtinNode(node *ast.BuiltinNode) Nature {
959:func (v *Checker) checkBuiltinGet(node *ast.BuiltinNode) Nature {
1272:func (v *Checker) sequenceNode(node *ast.SequenceNode) Nature {
1283:func (v *Checker) conditionalNode(node *ast.ConditionalNode) Nature {
