package form

import (
	"bytes"
	"encoding/json"
	"fmt"
	"reflect"
	"strings"
	"unicode/utf8"

	"github.com/df-mc/dragonfly/server/world"
)

// Form represents a form that may be sent to a Submitter. The three types of forms, custom forms, menu forms
// and modal forms implement this interface.
type Form interface {
	json.Marshaler
	SubmitJSON(b []byte, submitter Submitter, tx *world.Tx) error
}

// Custom represents a form that may be sent to a player and has fields that should be filled out by the
// player that the form is sent to.
type Custom struct {
	title       string
	submittable Submittable
}

// MarshalJSON ...
func (f Custom) MarshalJSON() ([]byte, error) {
	return json.Marshal(map[string]any{
		"type":    "custom_form",
		"title":   f.title,
		"content": f.Elements(),
	})
}

// New creates a new (custom) form with the title passed and returns it. The title is formatted according to
// the rules of fmt.Sprintln.
// The submittable passed is used to create the structure of the form. The values of the Submittable's form
// fields are used to set text, defaults and placeholders. If the Submittable passed is not a struct, New
// panics. New also panics if one of the exported field types of the Submittable is not one that implements
// the Element interface.
func New(submittable Submittable, title ...any) Custom {
	t := reflect.TypeOf(submittable)
	if t.Kind() != reflect.Struct {
		panic("submittable must be struct")
	}
	f := Custom{title: format(title), submittable: submittable}
	f.verify()
	return f
}

// Title returns the formatted title passed when the form was created using New().
func (f Custom) Title() string {
	return f.title
}

// Elements returns a list of all elements as set in the Submittable passed to form.New().
func (f Custom) Elements() []Element {
	v := reflect.New(reflect.TypeOf(f.submittable)).Elem()
	v.Set(reflect.ValueOf(f.submittable))
	n := v.NumField()

	elements := make([]Element, 0, n)
	for i := 0; i < n; i++ {
		field := v.Field(i)
		if !field.CanSet() {
			continue
		}
		// Each exported field is guaranteed to implement the Element interface.
		elements = append(elements, field.Interface().(Element))
	}
	return elements
}

// SubmitJSON submits a JSON data slice to the form. The form will check all values in the JSON array passed,
// making sure their values are valid for the form's elements.
// If the values are valid and can be parsed properly, the Submittable.Submit() method of the form's Submittable is
// called and the fields of the Submittable will be filled out.
func (f Custom) SubmitJSON(b []byte, submitter Submitter, tx *world.Tx) error {
	if b == nil {
		if closer, ok := f.submittable.(Closer); ok {
			closer.Close(submitter, tx)
		}
		return nil
	}

	dec := json.NewDecoder(bytes.NewBuffer(b))
	dec.UseNumber()

	var data []any
	if err := dec.Decode(&data); err != nil {
		return fmt.Errorf("error decoding JSON data to slice: %w", err)
	}

	v := reflect.New(reflect.TypeOf(f.submittable)).Elem()
	v.Set(reflect.ValueOf(f.submittable))

	for i := 0; i < v.NumField(); i++ {
		fieldV := v.Field(i)
		if !fieldV.CanSet() {
			continue
		}
		e := fieldV.Interface().(Element)
		if len(data) == 0 {
			return fmt.Errorf("form JSON data array does not have enough values")
		}
		if elementReadonly(e) {
			data = data[1:]
			continue
		}
		elem, err := f.parseValue(e, data[0])
		if err != nil {
			return fmt.Errorf("error parsing form response value: %w", err)
		}
		fieldV.Set(elem)
		data = data[1:]
	}

	v.Interface().(Submittable).Submit(submitter, tx)

	return nil
}

// elementReadonly returns true if the element is read only.
func elementReadonly(e Element) bool {
	switch e.(type) {
	case Divider, Header, Label:
		return true
	default:
		return false
	}
}

// parseValue parses a value into the Element passed and returns it as a reflection Value. If the value is not
// valid for the element, an error is returned.
func (f Custom) parseValue(elem Element, s any) (reflect.Value, error) {
	var ok bool
	var value reflect.Value

	switch element := elem.(type) {
	case Input:
		element.value, ok = s.(string)
		if !ok {
			return value, fmt.Errorf("value %v is not allowed for input element", s)
		}
		if !utf8.ValidString(element.value) {
			return value, fmt.Errorf("value %v is not valid UTF8", s)
		}
		value = reflect.ValueOf(element)
	case Toggle:
		element.value, ok = s.(bool)
		if !ok {
			return value, fmt.Errorf("value %v is not allowed for toggle element", s)
		}
		value = reflect.ValueOf(element)
	case Slider:
		v, ok := s.(json.Number)
		f, err := v.Float64()
		if !ok || err != nil {
			return value, fmt.Errorf("value %v is not allowed for slider element", s)
		}
		if f > element.Max || f < element.Min {
			return value, fmt.Errorf("slider value %v is out of range %v-%v", f, element.Min, element.Max)
		}
		element.value = f
		value = reflect.ValueOf(element)
	case Dropdown:
		v, ok := s.(json.Number)
		f, err := v.Int64()
		if !ok || err != nil {
			return value, fmt.Errorf("value %v is not allowed for dropdown element", s)
		}
		if f < 0 || int(f) >= len(element.Options) {
			return value, fmt.Errorf("dropdown value %v is out of range %v-%v", f, 0, len(element.Options)-1)
		}
		element.value = int(f)
		value = reflect.ValueOf(element)
	case StepSlider:
		v, ok := s.(json.Number)
		f, err := v.Int64()
		if !ok || err != nil {
			return value, fmt.Errorf("value %v is not allowed for dropdown element", s)
		}
		if f < 0 || int(f) >= len(element.Options) {
			return value, fmt.Errorf("dropdown value %v is out of range %v-%v", f, 0, len(element.Options)-1)
		}
		element.value = int(f)
		value = reflect.ValueOf(element)
	}
	return value, nil
}

// verify verifies if the form is valid, checking if the fields all implement the Element interface. It panics
// if the form is not valid.
func (f Custom) verify() {
	el := reflect.TypeOf((*Element)(nil)).Elem()

	v := reflect.New(reflect.TypeOf(f.submittable)).Elem()
	v.Set(reflect.ValueOf(f.submittable))

	t := reflect.TypeOf(f.submittable)
	for i := 0; i < v.NumField(); i++ {
		if !v.Field(i).CanSet() {
			continue
		}
		if !t.Field(i).Type.Implements(el) {
			panic("all exported fields must implement form.Element interface")
		}
	}
}

// format is a utility function to format a list of values to have spaces between them, but no newline at the
// end.
func format(a []any) string {
	return strings.TrimSuffix(strings.TrimSuffix(fmt.Sprintln(a...), "\n"), "\n")
}
