// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.

// operator-rel.go
package expr

import (
	"reflect"

	"git.portale-stac.it/go-pkg/expr/kern"
	"git.portale-stac.it/go-pkg/expr/scan"
	"git.portale-stac.it/go-pkg/expr/sym"
	"git.portale-stac.it/go-pkg/expr/types"
	"git.portale-stac.it/go-pkg/expr/types/array"
	"git.portale-stac.it/go-pkg/expr/types/boolean"
	"git.portale-stac.it/go-pkg/expr/types/fract"
	"git.portale-stac.it/go-pkg/expr/types/str"
)

//-------- equal term

func newEqualTerm(tk *scan.Token) (inst *scan.Term) {
	return &scan.Term{
		Tk:       *tk,
		Children: make([]*scan.Term, 0, 2),
		Position: scan.PosInfix,
		Priority: scan.PriRelational,
		EvalFunc: evalEqual,
	}
}

// type deepFuncTemplate func(a, b any) (eq bool, err error)

func equals(a, b any, deepCmp kern.DeepFuncTemplate) (eq bool, err error) {
	if types.IsNumOrFract(a) && types.IsNumOrFract(b) {
		if types.IsNumber(a) && types.IsNumber(b) {
			if types.IsInteger(a) && types.IsInteger(b) {
				li, _ := a.(int64)
				ri, _ := b.(int64)
				eq = li == ri
			} else {
				eq = types.NumAsFloat(a) == types.NumAsFloat(b)
			}
		} else {
			var cmp int
			if cmp, err = fract.CmpAnyFract(a, b); err == nil {
				eq = cmp == 0
			}
		}
	} else if deepCmp != nil && array.IsArray(a) && array.IsArray(b) {
		eq, err = deepCmp(a, b)
	} else {
		eq = reflect.DeepEqual(a, b)
	}

	return
}

func evalEqual(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
	var leftValue, rightValue any

	if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
		return
	}

	v, err = equals(leftValue, rightValue, nil)
	return
}

//-------- not equal term

func newNotEqualTerm(tk *scan.Token) (inst *scan.Term) {
	return &scan.Term{
		Tk:       *tk,
		Children: make([]*scan.Term, 0, 2),
		Position: scan.PosInfix,
		Priority: scan.PriRelational,
		EvalFunc: evalNotEqual,
	}
}

func evalNotEqual(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
	if v, err = evalEqual(ctx, opTerm); err == nil {
		b, _ := boolean.ToBool(v)
		v = !b
	}
	return
}

//-------- less term

func newLessTerm(tk *scan.Token) (inst *scan.Term) {
	return &scan.Term{
		Tk:       *tk,
		Children: make([]*scan.Term, 0, 2),
		Position: scan.PosInfix,
		Priority: scan.PriRelational,
		EvalFunc: evalLess,
	}
}

func lessThan(self *scan.Term, a, b any) (isLess bool, err error) {
	if types.IsNumOrFract(a) && types.IsNumOrFract(b) {
		if types.IsNumber(a) && types.IsNumber(b) {
			if types.IsInteger(a) && types.IsInteger(b) {
				li, _ := a.(int64)
				ri, _ := b.(int64)
				isLess = li < ri
			} else {
				isLess = types.NumAsFloat(a) < types.NumAsFloat(b)
			}
		} else {
			var cmp int
			if cmp, err = fract.CmpAnyFract(a, b); err == nil {
				isLess = cmp < 0
			}
		}
	} else if str.IsString(a) && str.IsString(b) {
		ls, _ := a.(string)
		rs, _ := b.(string)
		isLess = ls < rs
		// Inclusion test
	} else if array.IsArray(a) && array.IsArray(b) {
		aList, _ := a.(*array.ArrayType)
		bList, _ := b.(*array.ArrayType)
		isLess = len(*aList) < len(*bList) && bList.Contains(aList)
	} else {
		err = self.ErrIncompatibleTypes(a, b)
	}
	return
}

func evalLess(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
	var leftValue, rightValue any

	if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
		return
	}
	v, err = lessThan(opTerm, leftValue, rightValue)
	return
}

//-------- less or equal term

func newLessEqualTerm(tk *scan.Token) (inst *scan.Term) {
	return &scan.Term{
		Tk:       *tk,
		Children: make([]*scan.Term, 0, 2),
		Position: scan.PosInfix,
		Priority: scan.PriRelational,
		EvalFunc: evalLessEqual,
	}
}

func lessThanOrEqual(self *scan.Term, a, b any) (isLessEq bool, err error) {
	if isLessEq, err = lessThan(self, a, b); err == nil {
		if !isLessEq {
			if array.IsArray(a) && array.IsArray(b) {
				isLessEq, err = array.SameContent(a, b)
			} else {
				isLessEq, err = equals(a, b, nil)
			}
		}
	}
	return
}

func evalLessEqual(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
	var leftValue, rightValue any

	if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
		return
	}

	v, err = lessThanOrEqual(opTerm, leftValue, rightValue)

	return
}

//-------- greater term

func newGreaterTerm(tk *scan.Token) (inst *scan.Term) {
	return &scan.Term{
		Tk:       *tk,
		Children: make([]*scan.Term, 0, 2),
		Position: scan.PosInfix,
		Priority: scan.PriRelational,
		EvalFunc: evalGreater,
	}
}

func evalGreater(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
	var leftValue, rightValue any

	if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
		return
	}

	v, err = lessThan(opTerm, rightValue, leftValue)
	return
}

//-------- greater or equal term

func newGreaterEqualTerm(tk *scan.Token) (inst *scan.Term) {
	return &scan.Term{
		Tk:       *tk,
		Children: make([]*scan.Term, 0, 2),
		Position: scan.PosInfix,
		Priority: scan.PriRelational,
		EvalFunc: evalGreaterEqual,
	}
}

func evalGreaterEqual(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
	var leftValue, rightValue any

	if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
		return
	}

	v, err = lessThanOrEqual(opTerm, rightValue, leftValue)
	return
}

// init
func init() {
	scan.RegisterTermConstructor(sym.SymDoubleEqual, newEqualTerm)
	scan.RegisterTermConstructor(sym.SymNotEqual, newNotEqualTerm)
	scan.RegisterTermConstructor(sym.SymLess, newLessTerm)
	scan.RegisterTermConstructor(sym.SymLessOrEqual, newLessEqualTerm)
	scan.RegisterTermConstructor(sym.SymGreater, newGreaterTerm)
	scan.RegisterTermConstructor(sym.SymGreaterOrEqual, newGreaterEqualTerm)
}
