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

// builtin-string.go
package expr

import (
	"fmt"
	"io"
	"strings"

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

const (
	strParamOther = "other"
)

// --- Start of function definitions
func doJoinStr(funcName string, sep string, it kern.Iterator) (result any, err error) {
	var sb strings.Builder
	var v any
	for v, err = it.Next(); err == nil; v, err = it.Next() {
		if it.Index() > 0 {
			sb.WriteString(sep)
		}
		if s, ok := v.(string); ok {
			sb.WriteString(s)
		} else {
			err = kern.ErrExpectedGot(funcName, kern.TypeString, v)
			return
		}
	}
	if err == io.EOF {
		err = nil
		result = sb.String()
	}
	return
}

func joinStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	if sep, ok := args[kern.ParamSeparator].(string); ok {
		if v, exists := args[kern.ParamItem]; exists {
			argv := v.([]any)
			if len(argv) == 1 {
				if a, ok := argv[0].(*array.ArrayType); ok {
					result, err = doJoinStr(name, sep, NewListIterator(a, nil))
				} else if it, ok := argv[0].(kern.Iterator); ok {
					result, err = doJoinStr(name, sep, it)
				} else if s, ok := argv[0].(string); ok {
					result = s
				} else {
					err = kern.ErrInvalidParameterValue(name, kern.ParamItem, v)
				}
			} else {
				result, err = doJoinStr(name, sep, NewArrayIterator(argv))
			}
		}
	} else {
		err = kern.ErrWrongParamType(name, kern.ParamSeparator, kern.TypeString, args[kern.ParamSeparator])
	}
	return
}

func subStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	var start = 0
	var count = -1
	var source string
	var ok bool

	if source, ok = args[kern.ParamSource].(string); !ok {
		return nil, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
	}

	if start, err = types.ToGoInt(args[kern.ParamStart], name+"()"); err != nil {
		return
	}

	if count, err = types.ToGoInt(args[kern.ParamCount], name+"()"); err != nil {
		return
	}

	if start < 0 {
		start = len(source) + start
	}

	if count < 0 {
		count = len(source) - start
	}
	end := min(start+count, len(source))
	result = source[start:end]
	return
}

func trimStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	var source string
	var ok bool

	if source, ok = args[kern.ParamSource].(string); !ok {
		return nil, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
	}
	result = strings.TrimSpace(source)
	return
}

func startsWithStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	var source, prefix string
	var ok bool

	result = false

	if source, ok = args[kern.ParamSource].(string); !ok {
		return result, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
	}

	if prefix, ok = args[kern.ParamPrefix].(string); !ok {
		return result, kern.ErrWrongParamType(name, kern.ParamPrefix, kern.TypeString, args[kern.ParamPrefix])
	}
	if strings.HasPrefix(source, prefix) {
		result = true
	} else if v, exists := args[strParamOther]; exists {
		argv := v.([]any)
		for i, targetSpec := range argv {
			if target, ok := targetSpec.(string); ok {
				if strings.HasPrefix(source, target) {
					result = true
					break
				}
			} else {
				err = fmt.Errorf("target item nr %d is %s, string expected", i+1, kern.TypeName(targetSpec))
				break
			}
		}
	}
	return
}

func endsWithStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	var source, suffix string
	var ok bool

	result = false

	if source, ok = args[kern.ParamSource].(string); !ok {
		return result, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
	}

	if suffix, ok = args[kern.ParamSuffix].(string); !ok {
		return result, kern.ErrWrongParamType(name, kern.ParamSuffix, kern.TypeString, args[kern.ParamSuffix])
	}
	if strings.HasPrefix(source, suffix) {
		result = true
	} else if v, exists := args[strParamOther]; exists {
		argv := v.([]any)
		for i, targetSpec := range argv {
			if target, ok := targetSpec.(string); ok {
				if strings.HasSuffix(source, target) {
					result = true
					break
				}
			} else {
				err = fmt.Errorf("target item nr %d is %s, string expected", i+1, kern.TypeName(targetSpec))
				break
			}
		}
	}
	return
}

func splitStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	var source, sep string
	var count int = -1
	var parts []string
	var ok bool

	if source, ok = args[kern.ParamSource].(string); !ok {
		return result, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
	}

	if sep, ok = args[kern.ParamSeparator].(string); !ok {
		return nil, fmt.Errorf("separator param must be string, got %s (%v)", kern.TypeName(args[kern.ParamSeparator]), args[kern.ParamSeparator])
	}

	if count64, ok := args[kern.ParamCount].(int64); ok { // TODO replace type assertion with toInt()
		count = int(count64)
	} else {
		return nil, fmt.Errorf("part count must be integer, got %s (%v)", kern.TypeName(args[kern.ParamCount]), args[kern.ParamCount])
	}

	if count > 0 {
		parts = strings.SplitN(source, sep, count)
	} else if count < 0 {
		parts = strings.Split(source, sep)
	} else {
		parts = []string{}
	}
	a := make(array.ArrayType, len(parts))
	for i, part := range parts {
		a[i] = part
	}
	result = &a
	return
}

func upperStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	if source, ok := args[kern.ParamSource].(string); ok {
		result = strings.ToUpper(source)
	} else {
		err = kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
	}
	return
}

func lowerStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	if source, ok := args[kern.ParamSource].(string); ok {
		result = strings.ToLower(source)
	} else {
		err = kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
	}
	return
}

// --- End of function definitions

// Import above functions in the context
func ImportStringFuncs(ctx kern.ExprContext) {
	ctx.RegisterFunc("strJoin", kern.NewGolangFunctor(joinStrFunc), kern.TypeString, []kern.ExprFuncParam{
		kern.NewFuncParam(kern.ParamSeparator),
		kern.NewFuncParamFlag(kern.ParamItem, kern.PfRepeat),
	})

	ctx.RegisterFunc("strSub", kern.NewGolangFunctor(subStrFunc), kern.TypeString, []kern.ExprFuncParam{
		kern.NewFuncParam(kern.ParamSource),
		kern.NewFuncParamFlagDef(kern.ParamStart, kern.PfDefault, int64(0)),
		kern.NewFuncParamFlagDef(kern.ParamCount, kern.PfDefault, int64(-1)),
	})

	ctx.RegisterFunc("strSplit", kern.NewGolangFunctor(splitStrFunc), "list of "+kern.TypeString, []kern.ExprFuncParam{
		kern.NewFuncParam(kern.ParamSource),
		kern.NewFuncParamFlagDef(kern.ParamSeparator, kern.PfDefault, ""),
		kern.NewFuncParamFlagDef(kern.ParamCount, kern.PfDefault, int64(-1)),
	})

	ctx.RegisterFunc("strTrim", kern.NewGolangFunctor(trimStrFunc), kern.TypeString, []kern.ExprFuncParam{
		kern.NewFuncParam(kern.ParamSource),
	})

	ctx.RegisterFunc("strStartsWith", kern.NewGolangFunctor(startsWithStrFunc), kern.TypeBoolean, []kern.ExprFuncParam{
		kern.NewFuncParam(kern.ParamSource),
		kern.NewFuncParam(kern.ParamPrefix),
		kern.NewFuncParamFlag(strParamOther, kern.PfRepeat),
	})

	ctx.RegisterFunc("strEndsWith", kern.NewGolangFunctor(endsWithStrFunc), kern.TypeBoolean, []kern.ExprFuncParam{
		kern.NewFuncParam(kern.ParamSource),
		kern.NewFuncParam(kern.ParamSuffix),
		kern.NewFuncParamFlag(strParamOther, kern.PfRepeat),
	})

	ctx.RegisterFunc("strUpper", kern.NewGolangFunctor(upperStrFunc), kern.TypeString, []kern.ExprFuncParam{
		kern.NewFuncParam(kern.ParamSource),
	})

	ctx.RegisterFunc("strLower", kern.NewGolangFunctor(lowerStrFunc), kern.TypeString, []kern.ExprFuncParam{
		kern.NewFuncParam(kern.ParamSource),
	})
}

// Register the import function in the import-register.
// That will allow to import all function of this module by the "builtin" operator."
func init() {
	RegisterBuiltinModule("string", ImportStringFuncs, "string utilities")
}
