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

// operator-assign.go
package expr

import (
	"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/array"
	"git.portale-stac.it/go-pkg/expr/types/dict"
	"git.portale-stac.it/go-pkg/expr/types/list"
	"git.portale-stac.it/go-pkg/expr/util"
)

//-------- assign term

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

func evalAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
	v, err = generalEvalAssign(ctx, opTerm, false)
	return
}

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

func evalDeepCopyAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
	v, err = generalEvalAssign(ctx, opTerm, true)
	return
}

func assignCollectionItem(ctx kern.ExprContext, collectionTerm, keyListTerm *scan.Term, value any) (err error) {
	var collectionValue, keyListValue, keyValue any
	var keyList *array.ArrayType
	var ok bool

	if collectionValue, err = collectionTerm.Compute(ctx); err != nil {
		return
	}

	if keyListValue, err = keyListTerm.Compute(ctx); err != nil {
		return
	} else if keyList, ok = keyListValue.(*array.ArrayType); !ok || len(*keyList) != 1 {
		err = keyListTerm.Errorf("index/key specification expected, got %v [%s]", keyListValue, kern.TypeName(keyListValue))
		return
	}
	if keyValue = (*keyList)[0]; keyValue == nil {
		err = keyListTerm.Errorf("index/key is nil")
		return
	}

	switch collection := collectionValue.(type) {
	case *array.ArrayType:
		if index, ok := keyValue.(int64); ok {
			err = collection.SetItem(index, value)
		} else {
			err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, kern.TypeName(keyValue))
		}
	case *dict.DictType:
		collection.SetItem(keyValue, value)
	case *list.LinkedList:
		if index, ok := keyValue.(int64); ok {
			err = collection.SetItem(index, value)
		} else {
			err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, kern.TypeName(keyValue))
		}
	default:
		err = collectionTerm.Errorf("collection expected")
	}
	return
}

func assignValue(ctx kern.ExprContext, leftTerm *scan.Term, v any, deepCopy bool) (err error) {
	if leftTerm.Symbol() == sym.SymIndex {
		err = assignCollectionItem(ctx, leftTerm.Children[0], leftTerm.Children[1], v)
	} else {
		if deepCopy {
			v = kern.Clone(v)
		}
		ctx.UnsafeSetVar(leftTerm.Source(), v)
	}
	return
}

func evalAssignDictItem(ctx kern.ExprContext, dotTerm *scan.Term, valueTerm kern.Term) (v any, err error) {
	var ok bool
	var dictAny, dotKey any
	var dt *dict.DictType

	if err = dotTerm.CheckOperands(); err != nil {
		return
	}

	dotLeftTerm := dotTerm.GetChild(0)
	if dictAny, err = dotLeftTerm.Compute(ctx); err != nil {
		return
	}
	if dt, ok = dictAny.(*dict.DictType); !ok {
		err = dotTerm.Tk.ErrorExpectedGot(dict.DictTypeName)
		return
	}

	dotRightTerm := dotTerm.Children[1]
	dotRightSym := dotRightTerm.Symbol()
	if dotRightSym == sym.SymVariable || dotRightSym == sym.SymString {
		dotKey = util.UnquoteString(dotRightTerm.Source())
	} else if dotKey, err = dotRightTerm.Compute(ctx); err != nil {
		return
	}

	if v, err = valueTerm.Compute(ctx); err != nil {
		return
	}

	dt.SetItem(dotKey, v)
	return
}

func generalEvalAssign(ctx kern.ExprContext, opTerm *scan.Term, deepCopy bool) (v any, err error) {
	if err = opTerm.CheckOperands(); err != nil {
		return
	}

	leftTerm := opTerm.Children[0]
	leftSym := leftTerm.Symbol()
	if leftSym == sym.SymDot {
		return evalAssignDictItem(ctx, opTerm.Children[0], opTerm.GetChild(1))
	} else if leftSym != sym.SymVariable && leftSym != sym.SymIndex {
		err = leftTerm.Tk.Errorf("left operand of %q must be a variable or a collection's item", opTerm.Tk.Source())
		return
	}

	rightChild := opTerm.Children[1]

	if v, err = rightChild.Compute(ctx); err == nil {
		if functor, ok := v.(kern.Functor); ok {
			if leftSym == sym.SymVariable {
				if info := functor.GetFunc(); info != nil {
					ctx.RegisterFunc(leftTerm.Source(), info.Functor(), info.ReturnType(), info.Params())
				} else if funcDef, ok := functor.(*exprFunctor); ok {
					paramSpecs := util.ForAll(funcDef.params, func(p kern.ExprFuncParam) kern.ExprFuncParam { return p })

					ctx.RegisterFunc(leftTerm.Source(), functor, kern.TypeAny, paramSpecs)
				} else {
					err = opTerm.Errorf("unknown function %s()", rightChild.Source())
				}
			} else {
				err = assignValue(ctx, leftTerm, v, deepCopy)
			}
		} else {
			err = assignValue(ctx, leftTerm, v, deepCopy)
		}
	}
	if err != nil {
		v = nil
	}
	return
}

//-------- assign term

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

func getCollectionItemValue(ctx kern.ExprContext, collectionTerm, keyListTerm *scan.Term) (value any, err error) {
	var collectionValue, keyListValue, keyValue any
	var keyList *array.ArrayType
	var ok bool

	if collectionValue, err = collectionTerm.Compute(ctx); err != nil {
		return
	}

	if keyListValue, err = keyListTerm.Compute(ctx); err != nil {
		return
	} else if keyList, ok = keyListValue.(*array.ArrayType); !ok || len(*keyList) != 1 {
		err = keyListTerm.Errorf("index/key specification expected, got %v [%s]", keyListValue, kern.TypeName(keyListValue))
		return
	}
	if keyValue = (*keyList)[0]; keyValue == nil {
		err = keyListTerm.Errorf("index/key is nil")
		return
	}

	switch collection := collectionValue.(type) {
	case *array.ArrayType:
		if index, ok := keyValue.(int64); ok {
			value = (*collection)[index]
		} else {
			err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, kern.TypeName(keyValue))
		}
	case *dict.DictType:
		value = (*collection)[keyValue]
	default:
		err = collectionTerm.Errorf("collection expected")
	}
	return
}

func getAssignValue(ctx kern.ExprContext, leftTerm *scan.Term) (value any, err error) {
	if leftTerm.Symbol() == sym.SymIndex {
		value, err = getCollectionItemValue(ctx, leftTerm.Children[0], leftTerm.Children[1])
	} else {
		value, _ = ctx.GetVar(leftTerm.Source())
	}
	return
}

func evalOpAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
	var rightValue, leftValue any
	if err = opTerm.CheckOperands(); err != nil {
		return
	}

	leftTerm := opTerm.Children[0]
	leftSym := leftTerm.Symbol()
	if leftSym != sym.SymVariable && leftSym != sym.SymIndex {
		err = leftTerm.Tk.Errorf("left operand of %q must be a variable or a collection's item", opTerm.Tk.Source())
		return
	}

	rightChild := opTerm.Children[1]

	if rightValue, err = rightChild.Compute(ctx); err == nil {
		if leftValue, err = getAssignValue(ctx, leftTerm); err == nil {
			switch opTerm.Symbol() {
			case sym.SymPlusEqual:
				v, err = sumValues(opTerm, leftValue, rightValue)
			case sym.SymMinusEqual:
				v, err = diffValues(opTerm, leftValue, rightValue)
			case sym.SymStarEqual:
				v, err = mulValues(opTerm, leftValue, rightValue)
			case sym.SymSlashEqual:
				v, err = divValues(opTerm, leftValue, rightValue)
			case sym.SymPercEqual:
				v, err = remainderValues(opTerm, leftValue, rightValue)
			case sym.SymAmpersandEqual:
				v, err = bitwiseAnd(opTerm, leftValue, rightValue)
			case sym.SymVertBarEqual:
				v, err = bitwiseOr(opTerm, leftValue, rightValue)
			case sym.SymCaretEqual:
				v, err = bitwiseXor(opTerm, leftValue, rightValue)
			case sym.SymDoubleLessEqual:
				v, err = bitLeftShift(opTerm, leftValue, rightValue)
			case sym.SymDoubleGreaterEqual:
				v, err = bitRightShift(opTerm, leftValue, rightValue)
			default:
				err = opTerm.Errorf("unsupported assign operator %q", opTerm.Source())
			}
			if err == nil {
				err = assignValue(ctx, leftTerm, v, false)
			}
		}
	}
	return
}

// init
func init() {
	scan.RegisterTermConstructor(sym.SymEqual, newAssignTerm)
	scan.RegisterTermConstructor(sym.SymColonEqual, newDeepCopyAssignTerm)
	scan.RegisterTermConstructor(sym.SymPlusEqual, newOpAssignTerm)
	scan.RegisterTermConstructor(sym.SymMinusEqual, newOpAssignTerm)
	scan.RegisterTermConstructor(sym.SymStarEqual, newOpAssignTerm)
	scan.RegisterTermConstructor(sym.SymSlashEqual, newOpAssignTerm)
	scan.RegisterTermConstructor(sym.SymPercEqual, newOpAssignTerm)
	scan.RegisterTermConstructor(sym.SymDoubleLessEqual, newOpAssignTerm)
	scan.RegisterTermConstructor(sym.SymDoubleGreaterEqual, newOpAssignTerm)
	scan.RegisterTermConstructor(sym.SymAmpersandEqual, newOpAssignTerm)
	scan.RegisterTermConstructor(sym.SymVertBarEqual, newOpAssignTerm)
	scan.RegisterTermConstructor(sym.SymCaretEqual, newOpAssignTerm)
}
