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

// simple-func-store.go
package expr

import (
	"fmt"
	"slices"
	"strings"
)

type SimpleFuncStore struct {
	SimpleVarStore
	funcStore map[string]*funcInfo
}

type paramFlags uint16

const (
	pfOptional paramFlags = 1 << iota
	pfRepeat
)

type funcParamInfo struct {
	name         string
	flags        paramFlags
	defaultValue any
}

func newFuncParam(name string) *funcParamInfo {
	return &funcParamInfo{name: name}
}

func newFuncParamFlag(name string, flags paramFlags) *funcParamInfo {
	return &funcParamInfo{name: name, flags: flags}
}

func newFuncParamFlagDef(name string, flags paramFlags, defValue any) *funcParamInfo {
	return &funcParamInfo{name: name, flags: flags, defaultValue: defValue}
}

func (param *funcParamInfo) Name() string {
	return param.name
}

func (param *funcParamInfo) Type() string {
	return "any"
}

func (param *funcParamInfo) IsOptional() bool {
	return (param.flags & pfOptional) != 0
}

func (param *funcParamInfo) IsRepeat() bool {
	return (param.flags & pfRepeat) != 0
}

func (param *funcParamInfo) DefaultValue() any {
	return param.defaultValue
}

type funcInfo struct {
	name       string
	minArgs    int
	maxArgs    int
	functor    Functor
	params     []ExprFuncParam
	returnType string
}

func (info *funcInfo) Params() []ExprFuncParam {
	return info.params
}

func (info *funcInfo) ReturnType() string {
	return info.returnType
}

func (info *funcInfo) ToString(opt FmtOpt) string {
	var sb strings.Builder
	sb.WriteByte('(')
	if info.params != nil {
		for i, p := range info.params {
			if i > 0 {
				sb.WriteString(", ")
			}
			sb.WriteString(p.Name())
			if p.IsOptional() {
				sb.WriteByte('=')
				if s, ok := p.DefaultValue().(string); ok {
					sb.WriteByte('"')
					sb.WriteString(s)
					sb.WriteByte('"')
				} else {
					sb.WriteString(fmt.Sprintf("%v", p.DefaultValue()))
				}
			}
		}
	}
	if info.maxArgs < 0 {
		sb.WriteString(" ...")
	}
	sb.WriteString(") -> ")
	if len(info.returnType) > 0 {
		sb.WriteString(info.returnType)
	} else {
		sb.WriteString(typeAny)
	}
	return sb.String()
}

func (info *funcInfo) Name() string {
	return info.name
}

func (info *funcInfo) MinArgs() int {
	return info.minArgs
}

func (info *funcInfo) MaxArgs() int {
	return info.maxArgs
}

func (info *funcInfo) Functor() Functor {
	return info.functor
}

func NewSimpleFuncStore() *SimpleFuncStore {
	ctx := &SimpleFuncStore{
		SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)},
		funcStore:      make(map[string]*funcInfo),
	}
	//ImportBuiltinsFuncs(ctx)
	return ctx
}

func (ctx *SimpleFuncStore) Clone() ExprContext {
	svs := ctx.SimpleVarStore
	return &SimpleFuncStore{
		// SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
		SimpleVarStore: SimpleVarStore{varStore: svs.cloneVars()},
		funcStore:      CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }),
	}
}

func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
	sb.WriteString("funcs: {\n")
	first := true
	names := ctx.EnumFuncs(func(name string) bool { return true })
	slices.Sort(names)
	for _, name := range names {
		if first {
			first = false
		} else {
			sb.WriteByte(',')
			sb.WriteByte('\n')
		}
		value, _ := ctx.GetFuncInfo(name)
		sb.WriteString(strings.Repeat("\t", indent+1))
		sb.WriteString(name)
		//sb.WriteString("=")
		if formatter, ok := value.(Formatter); ok {
			sb.WriteString(formatter.ToString(0))
		} else {
			sb.WriteString(fmt.Sprintf("%v", value))
		}
	}
	sb.WriteString("\n}\n")
}

func (ctx *SimpleFuncStore) ToString(opt FmtOpt) string {
	var sb strings.Builder
	sb.WriteString(ctx.SimpleVarStore.ToString(opt))
	funcsCtxToBuilder(&sb, ctx, 0)
	return sb.String()
}

func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
	info, exists = ctx.funcStore[name]
	return
}

// func (ctx *SimpleFuncStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
// 	ctx.funcStore[name] = &funcInfo{name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor}
// }

func (ctx *SimpleFuncStore) RegisterFuncInfo(info ExprFunc) {
	ctx.funcStore[info.Name()], _ = info.(*funcInfo)
}

func (ctx *SimpleFuncStore) RegisterFunc2(name string, functor Functor, returnType string, params []ExprFuncParam) error {
	var minArgs = 0
	var maxArgs = 0
	if params != nil {
		for _, p := range params {
			if maxArgs == -1 {
				return fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name())
				// } else if p.IsRepeat() {
				// 	maxArgs = -1
				// 	continue
			}
			if p.IsOptional() {
				maxArgs++
			} else if maxArgs == minArgs {
				minArgs++
				maxArgs++
			} else {
				return fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name())
			}
			if p.IsRepeat() {
				maxArgs = -1
			}
		}
	}
	ctx.funcStore[name] = &funcInfo{
		name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, params: params,
	}
	return nil
}

func (ctx *SimpleFuncStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
	funcNames = make([]string, 0)
	for name := range ctx.funcStore {
		if acceptor != nil {
			if acceptor(name) {
				funcNames = append(funcNames, name)
			}
		} else {
			funcNames = append(funcNames, name)
		}
	}
	return
}

func (ctx *SimpleFuncStore) Call(name string, args []any) (result any, err error) {
	if info, exists := ctx.funcStore[name]; exists {
		functor := info.functor
		result, err = functor.Invoke(ctx, name, args)
	} else {
		err = fmt.Errorf("unknown function %s()", name)
	}
	return
}