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

// builtin-iterator.go
package expr

import (
	"fmt"
	"io"

	"git.portale-stac.it/go-pkg/expr/kern"
	"git.portale-stac.it/go-pkg/expr/types/boolean"
	"git.portale-stac.it/go-pkg/expr/types/dict"
)

const (
	iterParamOperator = "operator"
	iterParamVars     = "vars"
	iterVarStatus     = "status"
	iterParamStatus   = "status"
	iterVarAbort      = "__abort"
)

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

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

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

	var vars *dict.DictType
	if vars, ok = args[iterParamVars].(*dict.DictType); !ok && args[iterParamVars] != nil {
		// err = fmt.Errorf("parameter %q must be a dictionary, passed %v [%s]", iterParamVars, args[iterParamVars], kern.TypeName(args[iterParamVars]))
		err = errBadParamType(iterParamVars, kern.TypeDict, 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 errBadParamType(paramName, wantedType string, wantedValue any) error {
	return fmt.Errorf("type of %q must be %s, passed %v [%s]", paramName, wantedType, wantedValue, kern.TypeName(wantedValue))
}

func abortFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	if status, provided := args[iterParamStatus]; provided {
		ctx.UnsafeSetVar("@"+iterVarStatus, status)
	}
	ctx.UnsafeSetVar("@"+iterVarAbort, true)
	return
}

func runFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	var it kern.Iterator
	var op kern.Functor
	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
	}

	localCtx.RegisterFunc("abort", kern.NewGolangFunctor(abortFunc), kern.TypeAny, []kern.ExprFuncParam{
		kern.NewFuncParamFlagDef(iterParamStatus, kern.PfOptional, "aborted"),
	})

	params = map[string]any{kern.ParamIndex: 0, kern.ParamItem: nil}
	for item, err = it.Next(); err == nil; item, err = it.Next() {
		if op != nil {
			// params = map[string]any{kern.ParamIndex: it.Index(), kern.ParamItem: item}
			params[kern.ParamIndex] = it.Index()
			params[kern.ParamItem] = item

			localCtx.UnsafeSetVar("_", item)
			localCtx.UnsafeSetVar("__", it.Index())

			abort := false
			if _, err = op.InvokeNamed(localCtx, iterParamOperator, params); err != nil {
				abort = true
			} else if abortAny, exists := localCtx.GetVar(iterVarAbort); exists {
				if abort, ok := boolean.ToBool(abortAny); ok && abort {
					abort = true
				}
			}
			localCtx.DeleteVar("__")
			localCtx.DeleteVar("_")
			if abort {
				break
			}
		}
	}

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

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

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