package azalea

import (
	"errors"
	"fmt"
	"maps"
	"reflect"
	"slices"
	"strings"
	"unique"
)

// Value are types supported by the language.
type Value interface {
	bool | int64 | string | []string | []int64 | [][2]string
}

type (
	// FArg is an argument passed to [F].
	FArg struct {
		K unique.Handle[Ident]
		V any
		R bool
	}
	// FArgs are arguments passed to [F].
	FArgs []FArg

	// PF implements the package declaration function.
	PF func(name Ident, args FArgs) (v any, set bool, err error)

	// F is the implementation of a [Func].
	F struct {
		F func(args FArgs) (v any, set bool, err error)
		V map[unique.Handle[Ident]]any
	}
)

// Apply applies named arguments and rejects unused arguments.
func (args FArgs) Apply(v map[unique.Handle[Ident]]any) error {
	for _, arg := range args {
		if arg.V == nil {
			// unset
			continue
		}

		r, ok := v[arg.K]
		if !ok {
			if arg.R {
				continue
			}
			return UndefinedError(arg.K.Value())
		}
		err := storeE(r, arg.V)
		if err != nil {
			return err
		}
	}
	return nil
}

// A Frame refers to local variables and debugging information.
type Frame struct {
	// Local constants.
	Val map[unique.Handle[Ident]]any
	// Functions.
	Func map[unique.Handle[Ident]]F
}

// UnsupportedExprError is an expression with invalid concrete type.
type UnsupportedExprError struct{ E any }

func (e UnsupportedExprError) Error() string {
	return fmt.Sprintf("unsupported expression %#v", e.E)
}

// UndefinedError is an identifier not defined in any stack frame visible to the
// expression containing it.
type UndefinedError Ident

func (e UndefinedError) Error() string {
	return "undefined: " + string(e)
}

// evaluate is evaluateAny with a type parameter.
func evaluate[T Value](d PF, s []Frame, expr any, rp *T) bool {
	return evaluateAny(d, s, expr, rp)
}

// evaluateArray evaluates expr and returns its values as a slice.
func evaluateArray[T Value](d PF, s []Frame, expr Array) []T {
	r := make([]T, 0, len(expr))
	for i := range expr {
		var _r T
		if evaluate(d, s, expr[i], &_r) {
			r = append(r, _r)
		}
	}
	return r
}

// TypeError is an unexpected type during evaluation.
type TypeError struct {
	Concrete, Asserted reflect.Type
}

func (e TypeError) Error() string {
	return fmt.Sprintf("expected %s, got %s", e.Asserted, e.Concrete)
}

func (e TypeError) Is(err error) bool {
	v, ok := errors.AsType[TypeError](err)
	return ok &&
		e.Asserted == v.Asserted &&
		e.Concrete == v.Concrete
}

// storeE is a convenience function to set the value of a result pointer.
func storeE(rp any, r any) error {
	pv := reflect.ValueOf(rp).Elem()
	v := reflect.ValueOf(r)
	pt, vt := pv.Type(), v.Type()
	if !vt.AssignableTo(pt) {
		return TypeError{vt, pt}
	}
	pv.Set(v)
	return nil
}

// store is like storeE, but panics if error is non-nil.
func store[T Value](rp any, r T) {
	err := storeE(rp, r)
	if err != nil {
		panic(err)
	}
}

// EvaluationError is an error and the expression it occurred in.
type EvaluationError struct {
	Expr any
	Err  error
}

// Unwrap returns the underlying error.
func (e EvaluationError) Unwrap() error { return e.Err }

// Error returns a very long error description that should not be presented
// to the user directly.
func (e EvaluationError) Error() string {
	return fmt.Sprintf("expression %#v: %v", e.Expr, e.Err)
}

var (
	// IdentInputs is a special array argument in a package declaration whose
	// values of [Ident] are kept as is when passed to a [PF].
	IdentInputs = unique.Make(Ident("inputs"))
	// IdentRuntime has the same semantics as [IdentInputs].
	IdentRuntime = unique.Make(Ident("runtime"))
	// IdentExtra has the same semantics as [IdentInputs].
	IdentExtra = unique.Make(Ident("extra"))
	// IdentSource is a special argument in a package declaration where an
	// assignment of a [Val] with a single [Ident] passes through the evaluator.
	IdentSource = unique.Make(Ident("source"))

	// ErrInvalidSpecial is panicked for a special [PF] argument sharing its
	// value or set for R.
	ErrInvalidSpecial = errors.New("special must not be common or bound to scope")
)

// evaluateAny implements [Evaluate].
func evaluateAny(d PF, s []Frame, expr, rp any) bool {
	defer func() {
		r := recover()
		if r == nil {
			return
		}

		err, ok := r.(error)
		if !ok {
			panic(r)
		}

		if _, ok = err.(EvaluationError); ok {
			panic(err)
		}
		panic(EvaluationError{expr, err})
	}()

	switch e := expr.(type) {
	case Int:
		store(rp, int64(e))
		return true

	case String:
		store(rp, string(e))
		return true

	case Ident:
		var (
			v  any
			ok bool
		)
		for i := range s {
			v, ok = s[len(s)-1-i].Val[unique.Make(e)]
			if ok {
				break
			}
		}
		if !ok {
			panic(UndefinedError(e))
		}
		if err := storeE(rp, v); err != nil {
			panic(err)
		}
		return true

	case Val:
		if len(e) == 1 {
			switch v := e[0].(type) {
			case Ident:
				switch v {
				case "unset":
					return false
				case "true":
					store(rp, true)
					return true
				case "false":
					store(rp, false)
					return true
				default:
					return evaluateAny(d, s, v, rp)
				}

			default:
				return evaluateAny(d, s, e[0], rp)
			}
		}
		var v strings.Builder
		for i := range e {
			var _r string
			if evaluate(d, s, e[i], &_r) {
				v.WriteString(_r)
			}
		}
		store(rp, v.String())
		return true

	case Array:
		if len(e) > 0 && len(e[0]) == 1 {
			if _, ok := e[0][0].(Int); ok {
				store(rp, evaluateArray[int64](d, s, e))
				return true
			}
		}
		store(rp, evaluateArray[string](d, s, e))
		return true

	case []KV:
		r := make([][2]string, 0, len(e))
		for i := range e {
			var _r string
			if e[i].V == nil || evaluate(d, s, e[i].V, &_r) {
				r = append(r, [2]string{string(e[i].K), _r})
			}
		}
		store(rp, r)
		return true

	case Func:
		var (
			f  F
			ok bool
		)
		if !e.Package {
			for i := range s {
				f, ok = s[len(s)-1-i].Func[unique.Make(e.Ident)]
				if ok {
					break
				}
			}
			if !ok {
				panic(UndefinedError(e.Ident))
			}
		}

		argc := len(e.Args)
		for _, arg := range e.Args {
			argc += len(arg.K) - 1
		}
		fargs := make([]FArg, 0, len(e.Args))
		s = append(s, Frame{})
		fp := &s[len(s)-1]
		if !e.Package {
			fp.Val = maps.Clone(f.V)
		}

	args:
		for _, arg := range e.Args {
			names := make([]unique.Handle[Ident], len(arg.K))
			for i, name := range arg.K {
				names[i] = unique.Make(name)
			}

			farg := FArg{R: arg.R}
			if e.Package {
				for _, special := range [...]unique.Handle[Ident]{
					IdentInputs,
					IdentRuntime,
					IdentExtra,
				} {
					if slices.Contains(names, special) {
						if len(names) != 1 || len(arg.V) != 1 || arg.R {
							panic(ErrInvalidSpecial)
						}
						farg.K = names[0]
						if err := storeE(&farg.V, arg.V[0]); err != nil {
							panic(err)
						}
						fargs = append(fargs, farg)
						continue args
					}
				}

				if slices.Contains(names, IdentSource) {
					if len(names) != 1 || len(arg.V) != 1 || arg.R {
						panic(ErrInvalidSpecial)
					}
					if _, ok = arg.V[0].(Ident); ok {
						farg.K = names[0]
						if err := storeE(&farg.V, arg.V[0]); err != nil {
							panic(err)
						}
						fargs = append(fargs, farg)
						continue args
					}
				}
			}

			if !evaluateAny(d, s, arg.V, &farg.V) {
				farg.V = nil
			}
			for _, name := range names {
				farg.K = name
				fargs = append(fargs, farg)

				if arg.R && farg.V != nil {
					if fp.Val == nil {
						fp.Val = make(map[unique.Handle[Ident]]any)
					}
					(*fp).Val[name] = farg.V
				}
			}
		}

		var (
			v   any
			err error
		)
		if !e.Package {
			v, ok, err = f.F(fargs)
		} else {
			v, ok, err = d(e.Ident, fargs)
		}
		if err != nil {
			panic(err)
		} else if v != nil {
			if err = storeE(rp, v); err != nil {
				panic(err)
			}
		}
		return ok

	default:
		panic(UnsupportedExprError{expr})
	}
}

// Evaluate evaluates a statement and returns its value.
func Evaluate[T any](d PF, s []Frame, expr any) (v T, set bool, err error) {
	defer func() {
		r := recover()
		if r == nil {
			return
		}

		_err, ok := r.(error)
		if !ok {
			panic(r)
		}
		err = _err
	}()
	set = evaluateAny(d, s, expr, &v)
	return
}
