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

// operand-iterator.go
package expr

import (
	"slices"
	"strings"
)

// -------- iterator term

func newIteratorTerm(tk *Token, args []*term) *term {
	tk.Sym = SymIterator
	return &term{
		tk:       *tk,
		parent:   nil,
		children: args,
		position: posLeaf,
		priority: priValue,
		evalFunc: evalIterator,
	}
}

// -------- eval iterator

func evalTermArray(ctx ExprContext, terms []*term) (values []any, err error) {
	values = make([]any, len(terms))
	for i, t := range terms {
		var value any
		if value, err = t.compute(ctx); err == nil {
			values[i] = value
		} else {
			break
		}
	}
	return
}

func evalFirstChild(ctx ExprContext, iteratorTerm *term) (value any, err error) {
	if len(iteratorTerm.children) < 1 || iteratorTerm.children[0] == nil {
		err = iteratorTerm.Errorf("missing the data-source parameter")
		return
	}

	value, err = iteratorTerm.children[0].compute(ctx)
	return
}

func getDataSourceDict(iteratorTerm *term, firstChildValue any) (ds map[string]Functor, err error) {
	if dictAny, ok := firstChildValue.(*DictType); ok {
		requiredFields := []string{NextName}
		fieldsMask := 0b1
		foundFields := 0
		ds = make(map[string]Functor)
		for keyAny, item := range *dictAny {
			if key, ok := keyAny.(string); ok {
				if functor, ok := item.(Functor); ok {
					ds[key] = functor
					if index := slices.Index(requiredFields, key); index >= 0 {
						foundFields |= 1 << index
					}
				}
			}
		}
		// check required functions
		if foundFields != fieldsMask {
			missingFields := make([]string, 0, len(requiredFields))
			for index, field := range requiredFields {
				if (foundFields & (1 << index)) == 0 {
					missingFields = append(missingFields, field)
				}
			}
			err = iteratorTerm.children[0].Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", "))
		}
	}
	return
}

func evalIterator(ctx ExprContext, opTerm *term) (v any, err error) {
	var firstChildValue any
	var ds map[string]Functor

	if firstChildValue, err = evalFirstChild(ctx, opTerm); err != nil {
		return
	}

	if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil {
		return
	}

	if ds != nil {
		var dc *dataCursor
		dcCtx := ctx.Clone()
		if initFunc, exists := ds[InitName]; exists && initFunc != nil {
			var args []any
			var resource any
			if len(opTerm.children) > 1 {
				if args, err = evalTermArray(ctx, opTerm.children[1:]); err != nil {
					return
				}
			} else {
				args = []any{}
			}

			actualParams := bindActualParams(initFunc, args)

			initCtx := ctx.Clone()
			if resource, err = initFunc.InvokeNamed(initCtx, InitName, actualParams); err != nil {
				return
			}
			exportObjects(dcCtx, initCtx)
			dc = NewDataCursor(dcCtx, ds, resource)
		} else {
			dc = NewDataCursor(dcCtx, ds, nil)
		}

		v = dc
	} else if list, ok := firstChildValue.(*ListType); ok {
		var args []any
		if args, err = evalSibling(ctx, opTerm.children, nil); err == nil {
			v = NewListIterator(list, args)
		}
	} else {
		var list []any
		if list, err = evalSibling(ctx, opTerm.children, firstChildValue); err == nil {
			v = NewArrayIterator(list)
		}
	}
	return
}

func evalSibling(ctx ExprContext, terms []*term, firstChildValue any) (list []any, err error) {
	items := make([]any, 0, len(terms))
	for i, tree := range terms {
		var param any
		if i == 0 {
			if firstChildValue == nil {
				continue
			}
			param = firstChildValue
		} else if param, err = tree.compute(ctx); err != nil {
			break
		}
		items = append(items, param)
	}
	if err == nil {
		list = items
	}
	return
}
