// Copyright 2018 The CUE Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cue

import (
	"cuelang.org/go/cue/errors"
	"cuelang.org/go/cue/token"
	"cuelang.org/go/internal/core/adt"
)

func (v Value) toErr(b *adt.Bottom) (err errors.Error) {
	errs := errors.Errors(b.Err)
	if len(errs) > 1 {
		for _, e := range errs {
			bb := *b
			bb.Err = e
			err = errors.Append(err, &valueError{v: v, err: &bb})
		}
		return err
	}
	return &valueError{v: v, err: b}
}

var _ errors.Error = &valueError{}

// A valueError is returned as a result of evaluating a value.
type valueError struct {
	v   Value
	err *adt.Bottom
}

func (e *valueError) Unwrap() error {
	if e.err.Err == nil {
		return nil
	}
	return errors.Unwrap(e.err.Err)
}

func (e *valueError) Bottom() *adt.Bottom { return e.err }

func (e *valueError) Error() string {
	return errors.String(e)
}

func (e *valueError) Position() token.Pos {
	if e.err.Err != nil {
		return e.err.Err.Position()
	}
	return adt.Pos(e.err)
}

func (e *valueError) InputPositions() []token.Pos {
	if e.err.Err == nil {
		return nil
	}
	return e.err.Err.InputPositions()
}

func (e *valueError) Msg() (string, []interface{}) {
	if e.err.Err == nil {
		return "", nil
	}
	return e.err.Err.Msg()
}

func (e *valueError) Path() (a []string) {
	if e.err.Err != nil {
		a = e.err.Err.Path()
		if a != nil {
			return a
		}
	}
	return pathToStrings(e.v.Path())
}

var errNotExists = &adt.Bottom{
	Code:      adt.IncompleteError,
	NotExists: true,
	Err:       errors.Newf(token.NoPos, "undefined value"),
}

// IsIncomplete reports whether err is an incomplete error.
// Incomplete errors occur when a value cannot be fully evaluated
// due to missing information, such as unresolved references or
// incomplete disjunctions. These errors may be acceptable in
// non-concrete contexts.
//
// If the error returned by [Value.Err] is incomplete but the value
// still exists (i.e., [Value.Exists] returns true), it typically means
// the value is valid CUE but not fully resolved. In such cases,
// [Value.Validate] with default options will return nil.
func IsIncomplete(err error) bool {
	if err == nil {
		return false
	}
	// Fast path:
	if ve, ok := err.(*valueError); ok {
		return ve.err.IsIncomplete()
	}
	// Handle combined errors
	for _, e := range errors.Errors(err) {
		if ve, ok := e.(*valueError); ok {
			if ve.err.IsIncomplete() {
				return true
			}
		}
	}
	return false
}

func mkErr(src adt.Node, args ...interface{}) *adt.Bottom {
	var e *adt.Bottom
	var code adt.ErrorCode = -1
outer:
	for i, a := range args {
		switch x := a.(type) {
		case adt.ErrorCode:
			code = x
		case *adt.Bottom:
			e = adt.CombineErrors(nil, e, x)
		case []*adt.Bottom:
			for _, b := range x {
				e = adt.CombineErrors(nil, e, b)
			}
		case errors.Error:
			e = adt.CombineErrors(nil, e, &adt.Bottom{Err: x})
		case adt.Expr:
		case string:
			args := args[i+1:]
			// Do not expand message so that errors can be localized.
			pos := adt.Pos(src)
			if code < 0 {
				code = 0
			}
			e = adt.CombineErrors(nil, e, &adt.Bottom{
				Code: code,
				Err:  errors.Newf(pos, x, args...),
			})
			break outer
		}
	}
	if code >= 0 {
		e.Code = code
	}
	return e
}
