package binding

import (
	"errors"
	"reflect"

	"fyne.io/fyne/v2"
)

// DataMap is the base interface for all bindable data maps.
//
// Since: 2.0
type DataMap interface {
	DataItem
	GetItem(string) (DataItem, error)
	Keys() []string
}

// ExternalUntypedMap is a map data binding with all values untyped (any), connected to an external data source.
//
// Since: 2.0
type ExternalUntypedMap interface {
	UntypedMap
	Reload() error
}

// UntypedMap is a map data binding with all values Untyped (any).
//
// Since: 2.0
type UntypedMap interface {
	DataMap
	Delete(string)
	Get() (map[string]any, error)
	GetValue(string) (any, error)
	Set(map[string]any) error
	SetValue(string, any) error
}

// NewUntypedMap creates a new, empty map binding of string to any.
//
// Since: 2.0
func NewUntypedMap() UntypedMap {
	return &mapBase{items: make(map[string]reflectUntyped), val: &map[string]any{}}
}

// BindUntypedMap creates a new map binding of string to any based on the data passed.
// If your code changes the content of the map this refers to you should call Reload() to inform the bindings.
//
// Since: 2.0
func BindUntypedMap(d *map[string]any) ExternalUntypedMap {
	if d == nil {
		return NewUntypedMap().(ExternalUntypedMap)
	}
	m := &mapBase{items: make(map[string]reflectUntyped), val: d, updateExternal: true}

	for k := range *d {
		m.setItem(k, bindUntypedMapValue(d, k, m.updateExternal))
	}

	return m
}

// Struct is the base interface for a bound struct type.
//
// Since: 2.0
type Struct interface {
	DataMap
	GetValue(string) (any, error)
	SetValue(string, any) error
	Reload() error
}

// BindStruct creates a new map binding of string to any using the struct passed as data.
// The key for each item is a string representation of each exported field with the value set as an any.
// Only exported fields are included.
//
// Since: 2.0
func BindStruct(i any) Struct {
	if i == nil {
		return NewUntypedMap().(Struct)
	}
	t := reflect.TypeOf(i)
	if t.Kind() != reflect.Ptr ||
		(reflect.TypeOf(reflect.ValueOf(i).Elem()).Kind() != reflect.Struct) {
		fyne.LogError("Invalid type passed to BindStruct, must be pointer to struct", nil)
		return NewUntypedMap().(Struct)
	}

	s := &boundStruct{orig: i}
	s.items = make(map[string]reflectUntyped)
	s.val = &map[string]any{}
	s.updateExternal = true

	v := reflect.ValueOf(i).Elem()
	t = v.Type()
	for j := 0; j < v.NumField(); j++ {
		f := v.Field(j)
		if !f.CanSet() {
			continue
		}

		key := t.Field(j).Name
		s.items[key] = bindReflect(f)
		(*s.val)[key] = f.Interface()
	}

	return s
}

type reflectUntyped interface {
	DataItem
	get() (any, error)
	set(any) error
}

type mapBase struct {
	base

	updateExternal bool
	items          map[string]reflectUntyped
	val            *map[string]any
}

func (b *mapBase) GetItem(key string) (DataItem, error) {
	b.lock.RLock()
	defer b.lock.RUnlock()

	if v, ok := b.items[key]; ok {
		return v, nil
	}

	return nil, errKeyNotFound
}

func (b *mapBase) Keys() []string {
	b.lock.Lock()
	defer b.lock.Unlock()

	ret := make([]string, len(b.items))
	i := 0
	for k := range b.items {
		ret[i] = k
		i++
	}

	return ret
}

func (b *mapBase) Delete(key string) {
	b.lock.Lock()
	defer b.lock.Unlock()

	delete(b.items, key)

	b.trigger()
}

func (b *mapBase) Get() (map[string]any, error) {
	b.lock.RLock()
	defer b.lock.RUnlock()

	if b.val == nil {
		return map[string]any{}, nil
	}

	return *b.val, nil
}

func (b *mapBase) GetValue(key string) (any, error) {
	b.lock.RLock()
	defer b.lock.RUnlock()

	if i, ok := b.items[key]; ok {
		return i.get()
	}

	return nil, errKeyNotFound
}

func (b *mapBase) Reload() error {
	b.lock.Lock()
	defer b.lock.Unlock()

	return b.doReload()
}

func (b *mapBase) Set(v map[string]any) error {
	b.lock.Lock()
	defer b.lock.Unlock()

	if b.val == nil { // was not initialized with a blank value, recover
		b.val = &v
		b.trigger()
		return nil
	}

	*b.val = v
	return b.doReload()
}

func (b *mapBase) SetValue(key string, d any) error {
	b.lock.Lock()
	defer b.lock.Unlock()

	if i, ok := b.items[key]; ok {
		return i.set(d)
	}

	(*b.val)[key] = d
	item := bindUntypedMapValue(b.val, key, b.updateExternal)
	b.setItem(key, item)
	return nil
}

func (b *mapBase) doReload() (retErr error) {
	changed := false
	// add new
	for key := range *b.val {
		_, found := b.items[key]
		if !found {
			b.setItem(key, bindUntypedMapValue(b.val, key, b.updateExternal))
			changed = true
		}
	}

	// remove old
	for key := range b.items {
		_, found := (*b.val)[key]
		if !found {
			delete(b.items, key)
			changed = true
		}
	}
	if changed {
		b.trigger()
	}

	for k, item := range b.items {
		var err error

		if b.updateExternal {
			err = item.(*boundExternalMapValue).setIfChanged((*b.val)[k])
		} else {
			err = item.(*boundMapValue).set((*b.val)[k])
		}

		if err != nil {
			retErr = err
		}
	}
	return retErr
}

func (b *mapBase) setItem(key string, d reflectUntyped) {
	b.items[key] = d

	b.trigger()
}

type boundStruct struct {
	mapBase

	orig any
}

func (b *boundStruct) Reload() (retErr error) {
	b.lock.Lock()
	defer b.lock.Unlock()

	v := reflect.ValueOf(b.orig).Elem()
	t := v.Type()
	for j := 0; j < v.NumField(); j++ {
		f := v.Field(j)
		if !f.CanSet() {
			continue
		}
		kind := f.Kind()
		if kind == reflect.Slice || kind == reflect.Struct {
			fyne.LogError("Data binding does not yet support slice or struct elements in a struct", nil)
			continue
		}

		key := t.Field(j).Name
		old := (*b.val)[key]
		if f.Interface() == old {
			continue
		}

		var err error
		switch kind {
		case reflect.Bool:
			err = b.items[key].(*boundReflect[bool]).Set(f.Bool())
		case reflect.Float32, reflect.Float64:
			err = b.items[key].(*boundReflect[float64]).Set(f.Float())
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			err = b.items[key].(*boundReflect[int]).Set(int(f.Int()))
		case reflect.String:
			err = b.items[key].(*boundReflect[string]).Set(f.String())
		}
		if err != nil {
			retErr = err
		}
		(*b.val)[key] = f.Interface()
	}
	return retErr
}

func bindUntypedMapValue(m *map[string]any, k string, external bool) reflectUntyped {
	if external {
		ret := &boundExternalMapValue{old: (*m)[k]}
		ret.val = m
		ret.key = k
		return ret
	}

	return &boundMapValue{val: m, key: k}
}

type boundMapValue struct {
	base

	val *map[string]any
	key string
}

func (b *boundMapValue) get() (any, error) {
	if v, ok := (*b.val)[b.key]; ok {
		return v, nil
	}

	return nil, errKeyNotFound
}

func (b *boundMapValue) set(val any) error {
	(*b.val)[b.key] = val

	b.trigger()
	return nil
}

type boundExternalMapValue struct {
	boundMapValue

	old any
}

func (b *boundExternalMapValue) setIfChanged(val any) error {
	if val == b.old {
		return nil
	}
	b.old = val

	return b.set(val)
}

type boundReflect[T any] struct {
	base

	val reflect.Value
}

func (b *boundReflect[T]) Get() (T, error) {
	var zero T
	val, err := b.get()
	if err != nil {
		return zero, err
	}

	casted, ok := val.(T)
	if !ok {
		return zero, errors.New("unable to convert value to type")
	}

	return casted, nil
}

func (b *boundReflect[T]) Set(val T) error {
	return b.set(val)
}

func (b *boundReflect[T]) get() (any, error) {
	if !b.val.CanInterface() {
		return nil, errors.New("unable to get value from data binding")
	}

	return b.val.Interface(), nil
}

func (b *boundReflect[T]) set(val any) error {
	if !b.val.CanSet() {
		return errors.New("unable to set value in data binding")
	}

	b.val.Set(reflect.ValueOf(val))
	b.trigger()
	return nil
}

func bindReflect(field reflect.Value) reflectUntyped {
	switch field.Kind() {
	case reflect.Bool:
		return &boundReflect[bool]{val: field}
	case reflect.Float32, reflect.Float64:
		return &boundReflect[float64]{val: field}
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return &boundReflect[int]{val: field}
	case reflect.String:
		return &boundReflect[string]{val: field}
	}
	return &boundReflect[any]{val: field}
}
