// Copyright 2017-2022 The MathJax Consortium
// SPDX-License-Identifier: Apache-2.0
// Source: ts/input/tex/FilterUtil.ts (combineRelations).

package tex

import (
	

	
)

// relationUndefined distinguishes an absent JavaScript field (or an own field
// explicitly containing undefined) from an own null value in method inputs.
type relationUndefined struct{}

func relationField( any,  bool) any {
	if ! {
		return relationUndefined{}
	}
	return 
}

func relationNullish( any,  bool) bool {
	,  := .(relationUndefined)
	return ! ||  ||  == nil
}

func relationTruthy( any) bool {
	if ,  := .(relationUndefined);  {
		return false
	}
	return limitsTruthy()
}

// Attribute values are JS scalars. Go's numeric aliases represent one JS
// Number type; booleans, strings, null and undefined remain distinct. Internal
// object-valued properties compare by identity, never by structural equality.
func relationStrictEqual(,  any) bool {
	if  == nil ||  == nil {
		return  == nil &&  == nil
	}
	,  := reflect.ValueOf(), reflect.ValueOf()
	 := func( reflect.Value) (float64, bool) {
		switch .Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			return float64(.Int()), true
		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
			return float64(.Uint()), true
		case reflect.Float32, reflect.Float64:
			return .Float(), true
		}
		return 0, false
	}
	if ,  := ();  {
		,  := ()
		return  &&  == 
	}
	if .Type() != .Type() {
		return false
	}
	if .Kind() == reflect.Map {
		return .UnsafePointer() == .UnsafePointer()
	}
	return .Comparable() &&  == 
}

func relationCompareExplicit(,  *mml.Node) bool {
	 := func( *mml.Node,  string) []string {
		 := []string{}
		for ,  := range .Attributes.ExplicitNames() {
			,  := .Attributes.GetExplicit()
			if  !=  && ( != "stretchy" || relationTruthy()) {
				 = append(, )
			}
		}
		return 
	}
	,  := (, "lspace"), (, "rspace")
	if len() != len() {
		return false
	}
	// This asymmetry is intentional: the source compares only the left names,
	// after checking the counts, including a right name excluded above.
	for ,  := range  {
		if !relationStrictEqual(relationField(.Attributes.GetExplicit()), relationField(.Attributes.GetExplicit())) {
			return false
		}
	}
	return true
}

// combineRelations consumes the actual per-parse creation list. Parent-chain
// membership matches ParseOptions.getList; final-tree order is not equivalent.
func combineRelations( *mml.Node,  []*mml.Node) []*mml.Node {
	 := make([]*mml.Node, 0, len())
	for ,  := range  {
		for  := ;  != nil;  = .Parent {
			if  ==  {
				 = append(, )
				break
			}
		}
	}
	 := make(map[*mml.Node]bool)
	for ,  := range  {
		,  := .Property("relationsCombined")
		 := .Parent
		if relationTruthy() ||  == nil || .Kind != "mrow" || .TeXClass != mml.TeXClassRel {
			continue
		}
		 := 0
		for ,  := range .Children {
			if  ==  {
				 =  + 1
				break
			}
		}
		 := relationField(.Property("variantForm"))
		for  < len(.Children) {
			 := .Children[]
			if  == nil || .Kind != "mo" || .TeXClass != mml.TeXClassRel {
				break
			}
			if !relationStrictEqual(, relationField(.Property("variantForm"))) || !relationCompareExplicit(, ) {
				if relationNullish(.Attributes.GetExplicit("rspace")) {
					.Attributes.Set("rspace", "0pt")
				}
				if relationNullish(.Attributes.GetExplicit("lspace")) {
					.Attributes.Set("lspace", "0pt")
				}
				break
			}
			for ,  := range .Children {
				.AppendChild()
			}
			for ,  := range []string{"stretchy", "rspace"} {
				,  := .Attributes.GetExplicit()
				if !relationNullish(, ) {
					.Attributes.Set(, )
				}
			}
			.Properties.Range(func( string,  any) bool {
				.SetProperty(, )
				return true
			})
			.Children = append(.Children[:], .Children[+1:]...)
			.Parent = nil
			// MathJax reads these child-dependent flags through dynamic
			// getters. Refresh the Go caches before operatorForms or a later
			// filter iteration observes the newly combined row.
			for  := ;  != nil;  = .Parent {
				refreshDynamicFlags()
			}
			.SetProperty("relationsCombined", true)
			[] = true
		}
		.Attributes.SetInherited("form", operatorForms()[0])
	}
	 := [:0]
	for ,  := range  {
		if ![] {
			 = append(, )
		}
	}
	return 
}