package compiler

import (
	"sort"
	"strconv"

	"cuelang.org/go/cue"
	"cuelang.org/go/cue/ast"
	cueformat "cuelang.org/go/cue/format"
)

// Value is a wrapper around cue.Value.
// Use instead of cue.Value and cue.Instance
type Value struct {
	val cue.Value
	cc  *Compiler
}

// FillPath fills the value in-place
func (v *Value) FillPath(p cue.Path, x interface{}) error {
	v.cc.lock()
	defer v.cc.unlock()

	// If calling Fill() with a Value, we want to use the underlying
	// cue.Value to fill.
	if val, ok := x.(*Value); ok {
		v.val = v.val.FillPath(p, val.val)
	} else {
		v.val = v.val.FillPath(p, x)
	}
	return v.val.Err()
}

// LookupPath is a concurrency safe wrapper around cue.Value.LookupPath
func (v *Value) LookupPath(p cue.Path) *Value {
	v.cc.rlock()
	defer v.cc.runlock()

	return v.cc.Wrap(v.val.LookupPath(p))
}

// Lookup is a helper function to lookup by path parts.
func (v *Value) Lookup(path string) *Value {
	return v.LookupPath(cue.ParsePath(path))
}

func (v *Value) ReferencePath() (*Value, cue.Path) {
	vv, p := v.val.ReferencePath()
	return v.cc.Wrap(vv), p
}

// Proxy function to the underlying cue.Value
func (v *Value) Len() cue.Value {
	return v.val.Len()
}

// Proxy function to the underlying cue.Value
func (v *Value) Kind() cue.Kind {
	return v.val.Kind()
}

// Field represents a struct field
type Field struct {
	Selector cue.Selector
	Value    *Value
}

// Label returns the unquoted selector
func (f Field) Label() string {
	l := f.Selector.String()
	if unquoted, err := strconv.Unquote(l); err == nil {
		return unquoted
	}
	return l
}

// Proxy function to the underlying cue.Value
// Field ordering is guaranteed to be stable.
func (v *Value) Fields(opts ...cue.Option) ([]Field, error) {
	it, err := v.val.Fields(opts...)
	if err != nil {
		return nil, err
	}

	fields := []Field{}
	for it.Next() {
		fields = append(fields, Field{
			Selector: it.Selector(),
			Value:    v.cc.Wrap(it.Value()),
		})
	}

	sort.SliceStable(fields, func(i, j int) bool {
		return fields[i].Selector.String() < fields[j].Selector.String()
	})

	return fields, nil
}

// Proxy function to the underlying cue.Value
func (v *Value) Struct() (*cue.Struct, error) {
	return v.val.Struct()
}

// Proxy function to the underlying cue.Value
func (v *Value) Exists() bool {
	return v.val.Exists()
}

// Proxy function to the underlying cue.Value
func (v *Value) Bytes() ([]byte, error) {
	return v.val.Bytes()
}

// Proxy function to the underlying cue.Value
func (v *Value) String() (string, error) {
	return v.val.String()
}

// Proxy function to the underlying cue.Value
func (v *Value) Int64() (int64, error) {
	return v.val.Int64()
}

// Proxy function to the underlying cue.Value
func (v *Value) Path() cue.Path {
	return v.val.Path()
}

// Proxy function to the underlying cue.Value
func (v *Value) Decode(x interface{}) error {
	return v.val.Decode(x)
}

func (v *Value) List() ([]*Value, error) {
	l := []*Value{}
	it, err := v.val.List()
	if err != nil {
		return nil, err
	}
	for it.Next() {
		l = append(l, v.cc.Wrap(it.Value()))
	}

	return l, nil
}

// Recursive concreteness check.
func (v *Value) IsConcreteR(opts ...cue.Option) error {
	o := []cue.Option{cue.Concrete(true)}
	o = append(o, opts...)
	return v.val.Validate(o...)
}

func (v *Value) Walk(before func(*Value) bool, after func(*Value)) {
	// FIXME: lock?
	var (
		llBefore func(cue.Value) bool
		llAfter  func(cue.Value)
	)
	if before != nil {
		llBefore = func(child cue.Value) bool {
			return before(v.cc.Wrap(child))
		}
	}
	if after != nil {
		llAfter = func(child cue.Value) {
			after(v.cc.Wrap(child))
		}
	}
	v.val.Walk(llBefore, llAfter)
}

// Export concrete values to JSON. ignoring non-concrete values.
// Contrast with cue.Value.MarshalJSON which requires all values
// to be concrete.
func (v *Value) JSON() JSON {
	cuePathToStrings := func(p cue.Path) []string {
		selectors := p.Selectors()
		out := make([]string, len(selectors))
		for i, sel := range selectors {
			out[i] = sel.String()
		}
		return out
	}

	var out JSON
	v.val.Walk(
		func(v cue.Value) bool {
			b, err := v.MarshalJSON()
			if err == nil {
				newOut, err := out.Set(b, cuePathToStrings(v.Path())...)
				if err == nil {
					out = newOut
				}
				return false
			}
			return true
		},
		nil,
	)
	out, _ = out.Get(cuePathToStrings(v.Path())...)
	return out
}

// Check that a value is valid. Optionally check that it matches
// all the specified spec definitions..
func (v *Value) Validate() error {
	return v.val.Validate()
}

// Return cue source for this value
func (v *Value) Source(opts ...cue.Option) ([]byte, error) {
	v.cc.rlock()
	defer v.cc.runlock()

	return cueformat.Node(v.val.Eval().Syntax(opts...),
		cueformat.UseSpaces(4),
		cueformat.TabIndent(false),
	)
}

func (v *Value) IsEmptyStruct() bool {
	if st, err := v.Struct(); err == nil {
		if st.Len() == 0 {
			return true
		}
	}
	return false
}

func (v *Value) Cue() cue.Value {
	return v.val
}

// Returns true if value has a dagger attribute (eg. artifact, secret, input)
func (v *Value) HasAttr(filter ...string) bool {
	attrs := v.val.Attributes(cue.ValueAttr)

	for _, attr := range attrs {
		name := attr.Name()
		// match `@dagger(...)`
		if name == "dagger" {
			// did not provide filter, match any @dagger attr
			if len(filter) == 0 {
				return true
			}

			// loop over args (CSV content in attribute)
			for i := 0; i < attr.NumArgs(); i++ {
				key, _ := attr.Arg(i)
				// one or several values where provided, filter
				for _, val := range filter {
					if key == val {
						return true
					}
				}
			}
		}
	}

	return false
}

func (v *Value) Dereference() *Value {
	dVal := cue.Dereference(v.val)
	return v.cc.Wrap(dVal)
}

func (v *Value) Default() (*Value, bool) {
	val, hasDef := v.val.Default()
	return v.cc.Wrap(val), hasDef
}

func (v *Value) Doc() []*ast.CommentGroup {
	return v.Cue().Doc()
}

func (v *Value) IncompleteKindString() string {
	return v.Cue().IncompleteKind().String()
}
