package yqlib

type assignPreferences struct {
	DontOverWriteAnchor bool
	OnlyWriteNull       bool
	ClobberCustomTags   bool
}

func assignUpdateFunc(prefs assignPreferences) crossFunctionCalculation {
	return func(_ *dataTreeNavigator, _ Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
		if !prefs.OnlyWriteNull || lhs.Tag == "!!null" {
			lhs.UpdateFrom(rhs, prefs)
		}
		return lhs, nil
	}
}

// they way *= (multipleAssign) is handled, we set the multiplePrefs
// on the node, not assignPrefs. Long story.
func getAssignPreferences(preferences interface{}) assignPreferences {
	prefs := assignPreferences{}

	switch typedPref := preferences.(type) {
	case assignPreferences:
		prefs = typedPref
	case multiplyPreferences:
		prefs = typedPref.AssignPrefs
	}
	return prefs
}

func assignUpdateOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
	lhs, err := d.GetMatchingNodes(context, expressionNode.LHS)
	if err != nil {
		return Context{}, err
	}

	prefs := getAssignPreferences(expressionNode.Operation.Preferences)

	log.Debugf("assignUpdateOperator prefs: %v", prefs)

	if !expressionNode.Operation.UpdateAssign {
		// this works because we already ran against LHS with an editable context.
		_, err := crossFunction(d, context.ReadOnlyClone(), expressionNode, assignUpdateFunc(prefs), false)
		return context, err
	}

	//traverse backwards through the context -
	// like delete, we need to run against the children first.
	// (e.g. consider when running with expression '.. |= [.]' - we need
	// to wrap the children first
	for el := lhs.MatchingNodes.Back(); el != nil; el = el.Prev() {
		candidate := el.Value.(*CandidateNode)

		rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.RHS)

		if err != nil {
			return Context{}, err
		}

		// grab the first value
		first := rhs.MatchingNodes.Front()

		if first != nil {
			rhsCandidate := first.Value.(*CandidateNode)
			candidate.UpdateFrom(rhsCandidate, prefs)
		}
	}

	return context, nil
}

// does not update content or values
func assignAttributesOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
	log.Debug("getting lhs matching nodes for update")
	lhs, err := d.GetMatchingNodes(context, expressionNode.LHS)
	if err != nil {
		return Context{}, err
	}
	for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
		candidate := el.Value.(*CandidateNode)

		rhs, err := d.GetMatchingNodes(context.SingleReadonlyChildContext(candidate), expressionNode.RHS)

		if err != nil {
			return Context{}, err
		}

		// grab the first value
		first := rhs.MatchingNodes.Front()

		if first != nil {
			prefs := assignPreferences{}
			if expressionNode.Operation.Preferences != nil {
				prefs = expressionNode.Operation.Preferences.(assignPreferences)
			}
			if !prefs.OnlyWriteNull || candidate.Tag == "!!null" {
				candidate.UpdateAttributesFrom(first.Value.(*CandidateNode), prefs)
			}
		}
	}
	return context, nil
}
