package orderedmap

import (
	"fmt"
	"strings"

	"github.com/pb33f/libopenapi/datamodel/high/nodes"
	"github.com/pb33f/libopenapi/utils"
	"gopkg.in/yaml.v3"
)

type marshaler interface {
	MarshalYAML() (interface{}, error)
}

type NodeBuilder interface {
	AddYAMLNode(parent *yaml.Node, entry *nodes.NodeEntry) *yaml.Node
}

type MapToYamlNoder interface {
	ToYamlNode(n NodeBuilder, l any) *yaml.Node
}

type hasValueNode interface {
	GetValueNode() *yaml.Node
}

type hasValueUntyped interface {
	GetValueUntyped() any
}

type findValueUntyped interface {
	FindValueUntyped(k string) any
}

// ToYamlNode converts the ordered map to a yaml node ready for marshalling.
func (o *Map[K, V]) ToYamlNode(n NodeBuilder, l any) *yaml.Node {
	p := utils.CreateEmptyMapNode()
	if o != nil {
		p.Content = make([]*yaml.Node, 0)
	}

	var vn *yaml.Node

	i := 99999
	if l != nil {
		if hvn, ok := l.(hasValueNode); ok {
			vn = hvn.GetValueNode()
			if vn != nil && len(vn.Content) > 0 {
				i = vn.Content[0].Line
			}
		}
	}

	for pair := First(o); pair != nil; pair = pair.Next() {
		var k any = pair.Key()
		if m, ok := k.(marshaler); ok { // TODO marshal inline?
			mk, _ := m.MarshalYAML()
			b, _ := yaml.Marshal(mk)
			k = strings.TrimSpace(string(b))
		}

		ks := k.(string)

		var keyStyle yaml.Style
		keyNode := findKeyNode(ks, vn)
		if keyNode != nil {
			keyStyle = keyNode.Style
		}

		var lv any
		if l != nil {
			if hvut, ok := l.(hasValueUntyped); ok {
				vut := hvut.GetValueUntyped()
				if m, ok := vut.(findValueUntyped); ok {
					lv = m.FindValueUntyped(ks)
				}
			}
		}

		n.AddYAMLNode(p, &nodes.NodeEntry{
			Tag:      ks,
			Key:      ks,
			Line:     i,
			Value:    pair.Value(),
			KeyStyle: keyStyle,
			LowValue: lv,
		})
		i++
	}

	return p
}

func findKeyNode(key string, m *yaml.Node) *yaml.Node {
	if m == nil {
		return nil
	}

	for i := 0; i < len(m.Content); i += 2 {
		if m.Content[i].Value == key {
			return m.Content[i]
		}
	}
	return nil
}

// FindValueUntyped finds a value in the ordered map by key if the stored value for that key implements GetValueUntyped otherwise just returns the value.
func (o *Map[K, V]) FindValueUntyped(key string) any {
	for pair := First(o); pair != nil; pair = pair.Next() {
		var k any = pair.Key()
		if hvut, ok := k.(hasValueUntyped); ok {
			if fmt.Sprintf("%v", hvut.GetValueUntyped()) == key {
				return pair.Value()
			}
		}
		if fmt.Sprintf("%v", k) == key {
			return pair.Value()
		}
	}

	return nil
}
