// Copyright 2020 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 adt

import (
	"slices"
)

// Default returns the default value or itself if there is no default.
func Default(v Value) Value {
	switch x := v.(type) {
	case *Vertex:
		return x.Default()
	case *Disjunction:
		return x.Default()
	default:
		return v
	}
}

func (d *Disjunction) Default() Value {
	switch d.NumDefaults {
	case 0:
		return d
	case 1:
		return d.Values[0]
	default:
		return &Disjunction{
			Src:         d.Src,
			Values:      d.Values[:d.NumDefaults],
			NumDefaults: 0,
		}
	}
}

// Default returns the default value or itself if there is no default.
//
// It also closes a list, representing its default value.
func (v *Vertex) Default() *Vertex {
	v = v.DerefValue()
	switch d := v.BaseValue.(type) {
	default:
		return v

	case *Disjunction:
		var w *Vertex

		switch d.NumDefaults {
		case 0:
			return v
		case 1:
			w = ToVertex(Default(d.Values[0]))
			// If w already has conjuncts, return as-is to avoid race.
			if w.Conjuncts != nil {
				return w
			}
			// Make a copy before modifying to avoid racing on shared vertex.
			x := *w
			x.state = nil
			w = &x
		default:
			x := *v
			x.state = nil
			x.BaseValue = &Disjunction{
				Src:         d.Src,
				Values:      d.Values[:d.NumDefaults],
				NumDefaults: 0,
			}
			w = &x
		}

		// w is now a fresh copy, safe to modify without race.
		w.Conjuncts = make([]Conjunct, 0, len(v.Conjuncts))
		for _, c := range v.Conjuncts {
			node := stripNonDefaultsNode(c)
			w.Conjuncts = append(w.Conjuncts, MakeRootConjunct(c.Env, node))
		}
		return w

	case *ListMarker:
		if !d.IsOpen {
			// If the list is already closed, avoid the copies below.
			return v
		}
		m := *d
		m.IsOpen = false

		w := *v
		w.BaseValue = &m
		w.state = nil
		return &w
	}
}

// stripNonDefaults removes non-default values from disjunctions in the given
// expression. It returns the modified expression and whether any stripping
// occurred. For example, `*1 | 2 | 3` becomes `1`.
//
// TODO: this should go: record preexpanded disjunctions in Vertex.
func stripNonDefaults(elem Elem) (r Elem, stripped bool) {
	expr, ok := elem.(Expr)
	if !ok {
		return elem, false
	}
	switch x := expr.(type) {
	case *DisjunctionExpr:
		if !x.HasDefaults {
			return x, false
		}
		d := *x
		d.Values = []Disjunct{}
		for _, v := range x.Values {
			if v.Default {
				d.Values = append(d.Values, v)
			}
		}
		if len(d.Values) == 1 {
			return d.Values[0].Val, true
		}
		return &d, true

	case *BinaryExpr:
		if x.Op != AndOp {
			return x, false
		}
		a, sa := stripNonDefaults(x.X)
		b, sb := stripNonDefaults(x.Y)
		if sa || sb {
			bin := *x
			bin.X = a.(Expr)
			bin.Y = b.(Expr)
			return &bin, true
		}
		return x, false

	case *ConjunctGroup:
		// NOTE: this code requires allocations unconditional. This should be
		// mitigated once we optimize conjunct groupings.
		isNew := false
		a := slices.Clone(*x)
		for i, c := range a {
			a[i].x, ok = stripNonDefaults(c.Elem())
			if ok {
				isNew = true
			}
		}
		if isNew {
			return &a, true
		}
		return x, false

	default:
		return x, false
	}
}

// stripNonDefaultsNode is like stripNonDefaults but operates on a Conjunct
// and preserves field wrappers (Field, LetField, etc.) so that field metadata
// is retained when computing defaults.
func stripNonDefaultsNode(c Conjunct) Node {
	switch x := c.x.(type) {
	case *Field:
		if expr, stripped := stripNonDefaults(x.Value); stripped {
			f := *x
			f.Value = expr.(Expr)
			return &f
		}
		return x
	case *LetField:
		if expr, stripped := stripNonDefaults(x.Value); stripped {
			f := *x
			f.Value = expr.(Expr)
			return &f
		}
		return x
	case *BulkOptionalField:
		if expr, stripped := stripNonDefaults(x.Value); stripped {
			f := *x
			f.Value = expr.(Expr)
			return &f
		}
		return x
	case *DynamicField:
		if expr, stripped := stripNonDefaults(x.Value); stripped {
			f := *x
			f.Value = expr.(Expr)
			return &f
		}
		return x
	case *Ellipsis:
		if x.Value == nil {
			return x
		}
		if expr, stripped := stripNonDefaults(x.Value); stripped {
			e := *x
			e.Value = expr.(Expr)
			return &e
		}
		return x
	default:
		if elem, stripped := stripNonDefaults(c.Elem()); stripped {
			return elem
		}
		return c.x
	}
}
