// 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 map[string]any) (it Iterator, op Functor, err error) {
	var ok bool

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

	if op, ok = args[iterParamOperator].(Functor); !ok && args[iterParamOperator] != nil {
		err = fmt.Errorf("paramter %q must be a function, passed %v [%s]", iterParamOperator, args[iterParamOperator], TypeName(args[iterParamOperator]))
		return
	}

	var vars *DictType
	if vars, ok = args[iterParamVars].(*DictType); !ok && args[iterParamVars] != nil {
		err = fmt.Errorf("paramter %q must be a dictionary, passed %v [%s]", iterParamVars, args[iterParamVars], TypeName(args[iterParamVars]))
		return
	}

	if vars != nil {
		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 map[string]any) (result any, err error) {
	var it Iterator
	var ok bool
	var op Functor
	var v any
	var usingDefaultOp = false
	var params map[string]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 = map[string]any{ParamItem: []any{item}}
		} else {
			params = map[string]any{ParamIndex: it.Index(), ParamItem: item}
		}

		if v, err = op.InvokeNamed(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")
}