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

// builtin-iterator.go
package expr

import (
	"fmt"
	"io"
)

const (
	iterParamOperator = "operator"
	iterParamVars     = "vars"
	iterVarStatus     = "status"
)

func parseRunArgs(localCtx ExprContext, args []any) (it Iterator, op Functor, err error) {
	var ok bool

	if it, ok = args[0].(Iterator); !ok {
		err = fmt.Errorf("paramter %q must be an iterator, passed %v [%s]", ParamIterator, args[0], TypeName(args[0]))
		return
	}

	if len(args) > 1 {
		if op, ok = args[1].(Functor); !ok || op == nil {
			err = fmt.Errorf("paramter %q must be a function, passed %v [%s]", iterParamOperator, args[1], TypeName(args[1]))
			return
		}
		if len(args) > 2 {
			var vars *DictType
			if vars, ok = args[2].(*DictType); !ok || vars == nil {
				err = fmt.Errorf("paramter %q must be a dictionary, passed %v [%s]", iterParamVars, args[2], TypeName(args[2]))
				return
			}
			for key, value := range *vars {
				var varName string
				if varName, ok = key.(string); ok {
					localCtx.UnsafeSetVar(varName, value)
				}
			}
		}
	}
	return
}

func runFunc(ctx ExprContext, name string, args []any) (result any, err error) {
	var it Iterator
	var ok bool
	var op Functor
	var v any
	var usingDefaultOp = false
	var params []any
	var item any

	localCtx := ctx.Clone()
	localCtx.UnsafeSetVar(iterVarStatus, nil)

	if it, op, err = parseRunArgs(localCtx, args); err != nil {
		return
	} else if op == nil {
		op = NewGolangFunctor(printLnFunc)
		usingDefaultOp = true
	}

	for item, err = it.Next(); err == nil; item, err = it.Next() {
		if usingDefaultOp {
			params = []any{item}
		} else {
			params = []any{it.Index(), item}
		}

		if v, err = op.Invoke(localCtx, iterParamOperator, params); err != nil {
			break
		} else {
			var success bool
			if success, ok = ToBool(v); !success || !ok {
				break
			}
		}
	}

	if err == io.EOF {
		err = nil
	}
	if err == nil {
		result, _ = localCtx.GetVar(iterVarStatus)
	}
	return
}

func ImportIterFuncs(ctx ExprContext) {
	ctx.RegisterFunc("run", NewGolangFunctor(runFunc), TypeAny, []ExprFuncParam{
		NewFuncParam(ParamIterator),
		NewFuncParamFlag(iterParamOperator, PfOptional),
		NewFuncParamFlag(iterParamVars, PfOptional),
	})
}

func init() {
	RegisterBuiltinModule("iterator", ImportIterFuncs, "Iterator helper functions")
}