package svc

import (
	"errors"
	"fmt"
	"reflect"
	"strings"

	"golib/v3/features/mo"
	"golib/v3/infra/ii"
)

// toMaps
// 由于 mo.M 并非 map[string]interface{} 的别名, 而是重新定义的类型. 因此在实际开发环境中可能会出现混用的情况. 这时将无法直接使用断言
// 来确定类型: toMaps 即一劳永逸的解决各种底层为 map 类型的类型之间断言的问题
// 参数 f 提供一个操作函数, toMaps 会在循环时确定当前元素为 map 类型后将当前 map 传入 f 并调用: f(m)
// 函数 f 可以修改 m
// 最后 m 会保存至 docs 内
func (s *Service) toMaps(docs mo.A, f func(m mo.M) error) error {
	for i := 0; i < len(docs); i++ {
		if row, ok := docs[i].(mo.M); ok {
			if err := f(row); err != nil {
				s.Log.Error("svc.toMaps: the %d element handled: %s", i, err)
				return err
			}
		} else {
			b, err := mo.MarshalExtJSON(docs[i], true, true)
			if err != nil {
				s.Log.Error("svc.toMaps: the %d element MarshalExtJSON: %s", i, err)
				return err
			}
			var m mo.M
			if err = mo.UnmarshalExtJSON(b, true, &m); err != nil {
				s.Log.Error("svc.toMaps: the %d element Unmarshal: %s", i, err)
				return err
			}
			if err = f(m); err != nil {
				s.Log.Error("svc.toMaps: the %d element handled: %s", i, err)
				return err
			}
			docs[i] = m
		}
	}
	return nil
}

func (s *Service) checkBindType(v any) error {
	if reflect.TypeOf(v).Kind() != reflect.Ptr {
		return ErrBindTypeError
	}
	return nil
}

func splitPATH(path, prefix string) (string, ii.Name, error) {
	// "","item","insertOne","test.user"
	pathList := strings.Split(path, "/")
	if len(pathList) != 4 {
		return "", "", fmt.Errorf("err path: %s", path)
	}
	if pathList[1] != prefix {
		return "", "", errors.New("the first element of PATH must be: item")
	}
	return pathList[2], ii.Name(pathList[3]), nil
}

func Decode(row *Row, v any) error {
	return mo.Decode(row.Raw(), v)
}

func DecodeAll[T any](rows []*Row, dst *[]T) error {
	if len(*dst) < len(rows) {
		*dst = make([]T, len(rows))
	}
	for i, row := range rows {
		var v T
		if err := Decode(row, &v); err != nil {
			return err
		}
		(*dst)[i] = v
	}
	return nil
}

func Unmarshal(itemInfo *ii.ItemInfo, b []byte) (*Row, error) {
	var raw mo.M
	if err := mo.UnmarshalExtJSON(b, true, &raw); err != nil {
		return nil, err
	}
	row := make(mo.M)
	for _, field := range itemInfo.Fields {
		val, ok := raw[field.Name]
		if !ok {
			continue
		}
		v, err := field.Convert(val)
		if err != nil {
			return nil, err
		}
		row[field.Name] = v
	}
	return &Row{itemInfo: itemInfo, m: row}, nil
}
