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

// builtin-base.go
package expr

import (
	"fmt"
	"math"
	"strconv"
	"strings"

	"git.portale-stac.it/go-pkg/expr/kern"
	"git.portale-stac.it/go-pkg/expr/scan"
	"git.portale-stac.it/go-pkg/expr/types"
	"git.portale-stac.it/go-pkg/expr/types/array"
	"git.portale-stac.it/go-pkg/expr/types/boolean"
	"git.portale-stac.it/go-pkg/expr/types/dict"
	"git.portale-stac.it/go-pkg/expr/types/float"
	"git.portale-stac.it/go-pkg/expr/types/fract"
	"git.portale-stac.it/go-pkg/expr/types/list"
	"git.portale-stac.it/go-pkg/expr/types/str"
)

const (
	ParamDenominator = "denominator"
)

func isNilFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	result = args[kern.ParamValue] == nil
	return
}

func isIntFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	result = types.IsInteger(args[kern.ParamValue])
	return
}

func isFloatFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	result = float.IsFloat(args[kern.ParamValue])
	return
}

func isBoolFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	result = boolean.IsBool(args[kern.ParamValue])
	return
}

func isStringFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	result = str.IsString(args[kern.ParamValue])
	return
}

func isFractionFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	result = fract.IsFraction(args[kern.ParamValue])
	return
}

func isRationalFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	result = fract.IsRational(args[kern.ParamValue])
	return
}

func isArrayFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	result = array.IsArray(args[kern.ParamValue])
	return
}

func isDictionaryFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	result = dict.IsDict(args[kern.ParamValue])
	return
}

func isListFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	result = list.IsLinkedList(args[kern.ParamValue])
	return
}

func boolFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	switch v := args[kern.ParamValue].(type) {
	case int64:
		result = (v != 0)
	case *fract.FractionType:
		result = v.N() != 0
	case float64:
		result = v != 0.0
	case bool:
		result = v
	case string:
		result = len(v) > 0
	case *array.ArrayType:
		result = len(*v) > 0
	case *dict.DictType:
		result = len(*v) > 0
	default:
		err = kern.ErrCantConvert(name, v, "bool")
	}
	return
}

func intFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	switch v := args[kern.ParamValue].(type) {
	case int64:
		result = v
	case float64:
		result = int64(math.Trunc(v))
	case bool:
		if v {
			result = int64(1)
		} else {
			result = int64(0)
		}
	case string:
		var i int
		if i, err = strconv.Atoi(v); err == nil {
			result = int64(i)
		}
	case *fract.FractionType:
		result = int64(v.N() / v.D())
	default:
		err = kern.ErrCantConvert(name, v, "int")
	}
	return
}

func floatFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	switch v := args[kern.ParamValue].(type) {
	case int64:
		result = float64(v)
	case float64:
		result = v
	case bool:
		if v {
			result = float64(1)
		} else {
			result = float64(0)
		}
	case string:
		var f float64
		if f, err = strconv.ParseFloat(v, 64); err == nil {
			result = f
		}
	case *fract.FractionType:
		result = v.ToFloat()
	default:
		err = kern.ErrCantConvert(name, v, "float")
	}
	return
}

func stringFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	switch v := args[kern.ParamValue].(type) {
	case int64:
		result = strconv.FormatInt(v, 10)
	case float64:
		result = strconv.FormatFloat(v, 'g', -1, 64)
	case bool:
		if v {
			result = "true"
		} else {
			result = "false"
		}
	case string:
		result = v
	case *fract.FractionType:
		result = v.ToString(0)
	case kern.Formatter:
		result = v.ToString(0)
	case fmt.Stringer:
		result = v.String()
	default:
		err = kern.ErrCantConvert(name, v, "string")
	}
	return
}

func fractFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	switch v := args[kern.ParamValue].(type) {
	case int64:
		var den int64 = 1

		var ok bool
		if den, ok = args[ParamDenominator].(int64); !ok {
			err = kern.ErrExpectedGot(name, "integer", args[ParamDenominator])
		} else if den == 0 {
			err = kern.ErrFuncDivisionByZero(name)
		}

		if err == nil {
			result = fract.NewFraction(v, den)
		}
	case float64:
		result, err = fract.Float64ToFraction(v)
	case bool:
		if v {
			result = fract.NewFraction(1, 1)
		} else {
			result = fract.NewFraction(0, 1)
		}
	case string:
		result, err = fract.MakeGeneratingFraction(v)
	case *fract.FractionType:
		result = v
	default:
		err = kern.ErrCantConvert(name, v, "float")
	}
	return
}

// func iteratorFunc(ctx expr.ExprContext, name string, args []any) (result any, err error) {
// 	return
// }

func evalFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	if source, ok := args[kern.ParamSource].(string); ok {
		var ast kern.Expr

		parser := NewParser()
		if ctx == nil {
			ctx = NewSimpleStoreWithoutGlobalContext()
		}

		r := strings.NewReader(source)
		scanner := scan.NewScanner(r, scan.DefaultTranslations())

		if ast, err = parser.Parse(scanner); err == nil {
			CtrlEnable(ctx, kern.ControlExportAll)
			result, err = ast.Eval(ctx)
		}
	} else {
		err = kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
	}
	return
}

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

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

	if result, ok = args[kern.ParamValue]; ok && result != nil {
		ctx.GetParent().UnsafeSetVar(varName, result)
		// } else {
		// 	err = expr.ErrWrongParamType(name, expr.ParamSource, expr.TypeString, args[expr.ParamSource])
		// }
	} else if result, ok = ctx.GetVar(varName); !ok {
		err = kern.ErrUnknownVar(name, varName)
	}
	return
}

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

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

	if result, ok = args[kern.ParamValue]; ok {
		ctx.GetParent().UnsafeSetVar(varName, result)
	} else {
		err = kern.ErrWrongParamType(name, kern.ParamValue, kern.TypeAny, args[kern.ParamValue])
	}
	return
}

func charFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	var ord int

	if n, ok := args[kern.ParamValue].(byte); ok {
		ord = int(n)
	} else if n, ok := args[kern.ParamValue].(int64); ok {
		ord = int(n)
	} else if n, ok := args[kern.ParamValue].(int); ok {
		ord = n
	} else {
		return nil, kern.ErrWrongParamType(name, kern.ParamName, kern.TypeString, args[kern.ParamName])
	}
	if ord < 0 || ord > 255 {
		err = kern.ErrFuncInvalidArg(name, fmt.Sprintf("character code must be in range 0-255, got %d", ord))
	} else {
		result = string(rune(ord))
	}
	return
}

func seqFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
	list := list.NewLinkedList()
	items := args[kern.ParamValue].([]any)
	for _, arg := range items {
		list.PushBack(arg)
	}
	result = list
	return
}

//// import

func ImportBuiltinsFuncs(ctx kern.ExprContext) {
	anyParams := []kern.ExprFuncParam{
		kern.NewFuncParam(kern.ParamValue),
	}

	ctx.RegisterFunc("isNil", kern.NewGolangFunctor(isNilFunc), kern.TypeBoolean, anyParams)
	ctx.RegisterFunc("isInt", kern.NewGolangFunctor(isIntFunc), kern.TypeBoolean, anyParams)
	ctx.RegisterFunc("isFloat", kern.NewGolangFunctor(isFloatFunc), kern.TypeBoolean, anyParams)
	ctx.RegisterFunc("isBool", kern.NewGolangFunctor(isBoolFunc), kern.TypeBoolean, anyParams)
	ctx.RegisterFunc("isString", kern.NewGolangFunctor(isStringFunc), kern.TypeBoolean, anyParams)
	ctx.RegisterFunc("isFract", kern.NewGolangFunctor(isFractionFunc), kern.TypeBoolean, anyParams)
	ctx.RegisterFunc("isRational", kern.NewGolangFunctor(isRationalFunc), kern.TypeBoolean, anyParams)
	ctx.RegisterFunc("isArray", kern.NewGolangFunctor(isArrayFunc), kern.TypeBoolean, anyParams)
	ctx.RegisterFunc("isDict", kern.NewGolangFunctor(isDictionaryFunc), kern.TypeBoolean, anyParams)
	ctx.RegisterFunc("isList", kern.NewGolangFunctor(isListFunc), kern.TypeBoolean, anyParams)

	ctx.RegisterFunc("bool", kern.NewGolangFunctor(boolFunc), kern.TypeBoolean, anyParams)
	ctx.RegisterFunc("int", kern.NewGolangFunctor(intFunc), kern.TypeInt, anyParams)
	ctx.RegisterFunc("float", kern.NewGolangFunctor(floatFunc), kern.TypeFloat, anyParams)
	ctx.RegisterFunc("string", kern.NewGolangFunctor(stringFunc), kern.TypeString, anyParams)
	ctx.RegisterFunc("fract", kern.NewGolangFunctor(fractFunc), kern.TypeFraction, []kern.ExprFuncParam{
		kern.NewFuncParam(kern.ParamValue),
		kern.NewFuncParamFlagDef(ParamDenominator, kern.PfDefault, int64(1)),
	})

	ctx.RegisterFunc("eval", kern.NewGolangFunctor(evalFunc), kern.TypeAny, []kern.ExprFuncParam{
		kern.NewFuncParam(kern.ParamSource),
	})

	ctx.RegisterFunc("var", kern.NewGolangFunctor(varFunc), kern.TypeAny, []kern.ExprFuncParam{
		kern.NewFuncParam(kern.ParamName),
		kern.NewFuncParamFlagDef(kern.ParamValue, kern.PfDefault, nil),
	})

	ctx.RegisterFunc("set", kern.NewGolangFunctor(setFunc), kern.TypeAny, []kern.ExprFuncParam{
		kern.NewFuncParam(kern.ParamName),
		kern.NewFuncParam(kern.ParamValue),
	})

	ctx.RegisterFunc("char", kern.NewGolangFunctor(charFunc), kern.TypeString, []kern.ExprFuncParam{
		kern.NewFuncParam(kern.ParamValue),
	})

	ctx.RegisterFunc("seq", kern.NewGolangFunctor(seqFunc), kern.TypeLinkedList, []kern.ExprFuncParam{
		kern.NewFuncParamFlag(kern.ParamValue, kern.PfRepeat),
	})
}

func init() {
	RegisterBuiltinModule("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
}
