package field

import (
	"context"
	"fmt"
	"reflect"
	"strconv"

	"git.perx.ru/perxis/perxis-go/pkg/errors"
	"github.com/hashicorp/go-multierror"
)

var arrayType = &ArrayType{}

type ArrayParameters struct {
	Item *Field `json:"item"`
}

func (ArrayParameters) Type() Type { return arrayType }

func (p ArrayParameters) Clone(reset bool) Parameters {
	return &ArrayParameters{Item: p.Item.Clone(reset)}
}

func (a ArrayParameters) GetField(f *Field, name string) *Field {
	f.SetFieldState("Item", a.Item)

	if name == "" || name == "Item" {
		return a.Item
	}

	return a.Item.GetField(name)
}

func (a ArrayParameters) ListFields(f *Field, filterFunc ...FieldFilterFunc) []*Field {
	f.SetFieldState("Item", a.Item)

	return []*Field{a.Item}
}

type ArrayType struct{}

func (ArrayType) Name() string {
	return "array"
}

func (ArrayType) NewParameters() Parameters {
	return &ArrayParameters{}
}

func (ArrayType) IsEmpty(v interface{}) bool {
	arr, _ := v.([]interface{}) // todo: нужно возвращать ошибку?
	return len(arr) == 0
}

//func (ArrayType) Decode(ctx *Context.Context, field *Field, v interface{}) (interface{}, error) {
//	params, ok := field.Params.(*ArrayParameters)
//	if !ok {
//		return nil, errors.New("field parameters required")
//	}
//
//	arr, ok := v.([]interface{})
//	if !ok {
//		return nil, fmt.Errorf("[]interface{} required")
//	}
//
//	m := make([]interface{}, 0, len(arr))
//
//	for _, i := range arr {
//		item, err := Decode(ctx, params.Item, i)
//		if err != nil {
//			return nil, err
//		}
//		m = append(m, item)
//	}
//
//	return m, nil
//}
//
//func (ArrayType) Encode(ctx *Context.Context, field *Field, v interface{}) (interface{}, error) {
//	params, ok := field.Params.(*ArrayParameters)
//	if !ok {
//		return nil, errors.New("field parameters required")
//	}
//
//	arr, ok := v.([]interface{})
//	if !ok {
//		return nil, fmt.Errorf("[]interface{} required")
//	}
//
//	m := make([]interface{}, 0, len(arr))
//
//	for _, i := range arr {
//		item, err := params.Item.Encode(ctx, i)
//		if err != nil {
//			return nil, err
//		}
//		m = append(m, item)
//	}
//
//	return m, nil
//}

//func (ArrayType) Validate(ctx *Context.Context, field *Field, v interface{}) error {
//	params, ok := field.Params.(*ArrayParameters)
//	if !ok {
//		return errors.New("field parameters required")
//	}
//
//	m, ok := v.([]interface{})
//	if !ok {
//		return errors.New("[]interface{} is required")
//	}
//	for _, i := range m {
//		err := params.Item.Validate(ctx, i)
//		if err != nil {
//			return err
//		}
//	}
//	return nil
//}

func (ArrayType) Walk(ctx context.Context, field *Field, v interface{}, fn WalkFunc, opts *WalkOptions) (interface{}, bool, error) {
	var changed bool
	params, ok := field.Params.(*ArrayParameters)
	if !ok {
		return nil, false, errors.New("field parameters required")
	}

	// В массиве нет в данных и не выполняется обход по схеме
	if !opts.WalkSchema && v == nil {
		return nil, false, nil
	}

	arr, ok := v.([]interface{})
	if !ok && v != nil {
		return nil, false, fmt.Errorf("incorrect type: \"%s\", expected \"[]interface{}\"", reflect.ValueOf(v).Kind())
	}

	// Выполняется обход по схеме
	if opts.WalkSchema && len(arr) == 0 {
		_, _, _ = params.Item.Walk(ctx, nil, fn, WalkOpts(opts))
		return nil, false, nil
	}

	m := make([]interface{}, 0, len(arr))

	var merr *multierror.Error
	for i, value := range arr {

		valueNew, valueChanged, err := params.Item.Walk(ctx, value, fn, WalkOpts(opts))

		if err != nil {
			merr = multierror.Append(merr, errors.WithField(err, strconv.Itoa(i)))
		}

		if valueChanged {
			m = append(m, valueNew)
			changed = true
		} else {
			m = append(m, value)
		}
	}

	if merr != nil {
		merr.ErrorFormat = func(i []error) string {
			return fmt.Sprintf("%d error(s)", len(i))
		}
		return nil, false, merr
	}

	return m, changed, nil
}

func Array(item *Field, o ...interface{}) *Field {
	return NewField(&ArrayParameters{Item: item}, o...)
}