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

// operand-iterator.go
package expr

import (
	"slices"
	"strings"

	"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/interval"
	"git.portale-stac.it/go-pkg/expr/types/list"
)

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

func newIteratorTerm(tk *scan.Token, args []*scan.Term) *scan.Term {
	tk.Sym = sym.SymIterator
	return &scan.Term{
		Tk:       *tk,
		Parent:   nil,
		Children: args,
		Position: scan.PosLeaf,
		Priority: scan.PriValue,
		EvalFunc: evalIterator,
	}
}

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

func evalTermArray(ctx kern.ExprContext, terms []*scan.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 kern.ExprContext, iteratorTerm *scan.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 *scan.Term, firstChildValue any) (ds map[string]kern.Functor, err error) {
	if dictAny, ok := firstChildValue.(*dict.DictType); ok {
		requiredFields := []string{kern.NextName}
		fieldsMask := 0b1
		foundFields := 0
		ds = make(map[string]kern.Functor)
		for keyAny, item := range *dictAny {
			if key, ok := keyAny.(string); ok {
				if functor, ok := item.(kern.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 kern.ExprContext, opTerm *scan.Term) (v any, err error) {
	var firstChildValue any
	var ds map[string]kern.Functor

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

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

	if ds != nil {
		if len(ds) > 0 {
			var dc *dataCursor
			dcCtx := ctx.Clone()
			if initFunc, exists := ds[kern.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 := kern.BindActualParams(initFunc, args)

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

			v = dc
		} else {
			if dictIt, ok := firstChildValue.(*dict.DictType); ok {
				var args []any
				if args, err = evalSiblings(ctx, opTerm.Children, nil); err == nil {
					v, err = NewDictIterator(dictIt, args)
				}
			} else {
				err = opTerm.Children[0].Errorf("the data-source must be a dictionary")
			}
		}
	} else if a, ok := firstChildValue.(*array.ArrayType); ok {
		var args []any
		if args, err = evalSiblings(ctx, opTerm.Children, nil); err == nil {
			v = NewArrayIteratorFromArray(a, args)
		}
	} else if ll, ok := firstChildValue.(*list.LinkedList); ok {
		var args []any
		if args, err = evalSiblings(ctx, opTerm.Children, nil); err == nil {
			v = NewLinkedListIterator(ll, args)
		}
	} else if intervalVal, ok := firstChildValue.(*interval.IntervalType); ok {
		v, err = NewIntIteratorFromInterval(intervalVal)
		// } else if intVal, ok := firstChildValue.(int64); ok {
		// 	var args []any
		// 	if args, err = evalSiblings(ctx, opTerm.Children, intVal); err == nil {
		// 		v, err = NewIntIterator(args)
		// 	}
	} else if it, ok := firstChildValue.(kern.Iterator); ok {
		v, err = NewIterIter(it, ctx, opTerm.Children[1:])
	} else {
		var siblings []any
		if siblings, err = evalSiblings(ctx, opTerm.Children, firstChildValue); err == nil {
			v = NewArrayIterator(siblings)
		}
	}
	return
}

// func evalIterIter(ctx kern.ExprContext, firstChildValue any, siblings []any) (v any, err error) {
// 	var op kern.Functor
// 	var args map[string]any

// 	if it, ok := firstChildValue.(kern.Iterator); ok {
// 		if len(siblings) > 1 {
// 			if op, ok = siblings[1].(kern.Functor); ok {
// 				args = make(map[string]any, len(siblings)-2)
// 				for i, arg := range siblings[2:] {
// 					switch a := arg.(type) {
// 					case *kern.DictType:
// 						for keyAny, item := range *a {
// 							if key, ok := keyAny.(string); ok {
// 								args[key] = item
// 							}
// 						}
// 					default:
// 						args["arg"+strconv.Itoa(i+1)] = arg
// 					}
// 				}
// 			} else if op == nil {
// 				return nil, fmt.Errorf("the first sibling parameter must be a functor to be used as operation for the iterator")
// 			}
// 		}
// 		v, err = NewIterIter(it, ctx, op, args)
// 	}
// 	return
// }

func evalSiblings(ctx kern.ExprContext, terms []*scan.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
}
