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

// operator-insert.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/list"
)

//-------- prepend term

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

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

func prependToList(opTerm kern.Term, leftValue, rightValue any) (result any, err error) {
	if a, ok := rightValue.(*array.ArrayType); ok {
		var it kern.Iterator
		if it, ok = leftValue.(kern.Iterator); !ok {
			it = NewFormalIterator(leftValue)
		}

		ls := list.NewLinkedList()
		ls.SeqPushBack(it)

		newArray := array.ArrayType(nil)
		if newSize := len(*a) + int(ls.Len()); newSize > cap(*a) {
			newArray = make([]any, 0, newSize)
			for node := ls.FirstNode(); node != nil; node = node.Next() {
				newArray = append(newArray, node.Data())
			}
		}
		for _, item := range *a {
			newArray = append(newArray, item)
		}
		if opTerm.GetRightChild().IsVar() {
			// Update the original array if the right operand is a variable
			*a = newArray
		}
		result = &newArray
	} else if ll, ok := rightValue.(*list.LinkedList); ok {
		var it kern.Iterator
		if it, ok = leftValue.(kern.Iterator); !ok {
			it = NewFormalIterator(leftValue)
		}

		result = ll.SeqPushBack(it)
	} else {
		err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
	}
	return
}

func evalPrepend(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 = prependToList(opTerm, leftValue, rightValue)
	return
}

func appendToList(opTerm kern.Term, leftValue, rightValue any) (result any, err error) {
	if a, ok := leftValue.(*array.ArrayType); ok {
		var it kern.Iterator
		if it, ok = rightValue.(kern.Iterator); !ok {
			it = NewFormalIterator(rightValue)
		}

		ls := list.NewLinkedList()
		ls.SeqPushBack(it)

		newArray := *a
		if newSize := len(*a) + int(ls.Len()); newSize > cap(*a) {
			newArray = make([]any, 0, newSize)
			for _, item := range *a {
				newArray = append(newArray, item)
			}
		}
		for node := ls.FirstNode(); node != nil; node = node.Next() {
			newArray = append(newArray, node.Data())
		}
		if opTerm.GetLeftChild().IsVar() {
			// Update the original array if the left operand is a variable
			*a = newArray
		}
		result = &newArray
	} else if ll, ok := leftValue.(*list.LinkedList); ok {
		var it kern.Iterator
		if it, ok = rightValue.(kern.Iterator); !ok {
			it = NewFormalIterator(rightValue)
		}

		result = ll.SeqPushBack(it)
	} else {
		err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
	}
	return
}

func evalAppend(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 = appendToList(opTerm, leftValue, rightValue)

	return
}

// init
func init() {
	scan.RegisterTermConstructor(sym.SymPlusGreater, newPrependTerm)
	scan.RegisterTermConstructor(sym.SymLessPlus, newAppendTerm)
}
