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

// operand-iterator.go
package expr

import (
	"fmt"
	"slices"
	"strings"
)

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

func newDsIteratorTerm(tk *Token, dsTerm *term, args []*term) *term {
	tk.Sym = SymIterator

	children := make([]*term, 0, 1+len(args))
	children = append(children, dsTerm)
	children = append(children, args...)
	return &term{
		tk:       *tk,
		parent:   nil,
		children: children,
		position: posLeaf,
		priority: priValue,
		evalFunc: evalIterator,
	}
}

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, a []*term) (values []any, err error) {
	values = make([]any, len(a))
	for i, t := range a {
		var value any
		if value, err = t.compute(ctx); err == nil {
			values[i] = value
		} else {
			break
		}
	}
	return
}

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

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

func getDataSourceDict(ctx ExprContext, self *term, firstChildValue any) (ds map[string]Functor, err error) {
	if dictAny, ok := firstChildValue.(map[any]any); ok {
		requiredFields := []string{currentName, nextName}
		fieldsMask := 0b11
		foundFields := 0
		ds = make(map[string]Functor)
		for keyAny, item := range dictAny {
			if key, ok := keyAny.(string); ok {
				if functor, ok := item.(*funcDefFunctor); 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 = fmt.Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", "))
		}
	}
	return
}

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

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

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

	if ds != nil {
		dc := newDataCursor(ctx, ds)
		if initFunc, exists := ds[initName]; exists && initFunc != nil {
			var args []any
			if len(self.children) > 1 {
				if args, err = evalTermArray(ctx, self.children[1:]); err != nil {
					return
				}
			} else {
				args = []any{}
			}

			initCtx := dc.ctx.Clone()
			if dc.resource, err = initFunc.Invoke(initCtx, initName, args); err != nil {
				return
			}
			exportObjects(dc.ctx, initCtx)
		}

		dc.nextFunc, _ = ds[nextName]
		dc.currentFunc, _ = ds[currentName]
		dc.cleanFunc, _ = ds[cleanName]
		dc.resetFunc, _ = ds[resetName]

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

// func evalChildren(ctx ExprContext, terms []*term, firstChildValue any) (list *ListType, err error) {
// 	items := make(ListType, len(terms))
// 	for i, tree := range terms {
// 		var param any
// 		if i == 0 && firstChildValue != nil {
// 			param = firstChildValue
// 		} else if param, err = tree.compute(ctx); err != nil {
// 			break
// 		}
// 		items[i] = param
// 	}
// 	if err == nil {
// 		list = &items
// 	}
// 	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
}