// Copyright 2017-2022 The MathJax Consortium
// SPDX-License-Identifier: Apache-2.0
// This file is a Go translation and modification of MathJax 3.2.2.

package svg

import 

// prepareTeXClasses ports the MmlNode.setTeXclass() traversal.  Its return
// value is deliberately not always the node being visited: transparent rows
// return their final child, left/right rows return their closing delimiter,
// and mfrac returns a node whose TeX class is null.  Those distinctions are
// observable as inter-atom spacing in the SVG output.
func prepareTeXClasses( *mml.Node) {
	setTeXClass(, nil)
}

func setTeXClass(,  *mml.Node) *mml.Node {
	if  == nil {
		return 
	}
	switch .Kind {
	case "text", "XML", "xml", "annotation", "annotation-xml", "mspace", "mprescripts", "none":
		return 

	case "math", "mstyle", "mpadded", "mphantom":
		if len(.Children) == 0 {
			return 
		}
		 = (.Children[0], )
		updateTeXClass(, .Children[0])
		return 

	case "mrow":
		return setRowTeXClass(, )

	case "mfenced":
		setPreviousClass(, )
		if .Fenced != nil && .Fenced.Open != nil {
			 = (.Fenced.Open, )
		}
		for ,  := range .Children {
			if  > 0 && .Fenced != nil && -1 < len(.Fenced.Separators) {
				 = (.Fenced.Separators[-1], )
			}
			 = (, )
		}
		if .Fenced != nil {
			if .Fenced.Close != nil {
				 = (.Fenced.Close, )
			}
			updateTeXClass(, .Fenced.Open)
		}
		return 

	case "mfrac", "msqrt", "mroot", "mtable", "mtr", "mlabeledtr", "mtd":
		setPreviousClass(, )
		for ,  := range .Children {
			(, nil)
		}
		return 

	case "menclose":
		if len(.Children) == 0 {
			return 
		}
		 = (.Children[0], )
		updateTeXClass(, .Children[0])
		return 

	case "TeXAtom":
		if len(.Children) != 0 {
			(.Children[0], nil)
		}
		return adjustTeXClass(, )

	case "mi":
		setPreviousClass(, )
		,  := .Property("autoOP")
		,  := .Property("texClass")
		if ! && ! && stringAttribute(, "mathvariant", "") == "normal" && isIdentifierOperatorName(nodeText()) {
			.TeXClass = mml.TeXClassOp
			.SetProperty("autoOP", true)
		}
		return 

	case "mo":
		// Explicit MathML spacing without an assigned TeX class stops the
		// inter-atom TeX spacing chain (MmlMo.setTeXclass).
		if ,  := .Property("texClass"); ! &&
			(.Attributes.IsSet("lspace") || .Attributes.IsSet("rspace")) {
			return nil
		}
		return adjustTeXClass(, )

	case "msub", "msup", "msubsup", "munder", "mover", "munderover", "mmultiscripts", "semantics":
		return setBaseTeXClass(, )

	case "maction":
		 := actionSelection()
		if  == nil {
			return 
		}
		 = (, )
		updateTeXClass(, )
		return 
	}

	setPreviousClass(, )
	if .TeXClass == mml.TeXClassNone {
		return 
	}
	return 
}

// MmlMi.operatorName is the non-Unicode JavaScript /^[a-z][a-z0-9]*$/i.
// Only multi-character ASCII names participate in automatic OP spacing.
func isIdentifierOperatorName( string) bool {
	if len() < 2 {
		return false
	}
	for  := 0;  < len(); ++ {
		 := []
		if  >= 'a' &&  <= 'z' ||  >= 'A' &&  <= 'Z' ||  > 0 &&  >= '0' &&  <= '9' {
			continue
		}
		return false
	}
	return true
}

func setRowTeXClass(,  *mml.Node) *mml.Node {
	,  := .Property("open")
	,  := .Property("close")
	if  ||  {
		setPreviousClass(, )
		 = nil
	}
	for ,  := range .Children {
		 = setTeXClass(, )
	}
	if  ||  {
		if .TeXClass == mml.TeXClassNone {
			.TeXClass = mml.TeXClassInner
		}
	} else if len(.Children) != 0 {
		updateTeXClass(, .Children[0])
	}
	return 
}

func setBaseTeXClass(,  *mml.Node) *mml.Node {
	setPreviousClass(, )
	.TeXClass = mml.TeXClassOrd
	if len(.Children) == 0 || .Children[0] == nil {
		return 
	}
	 := .Children[0]
	if .Flags.Embellished || .Kind == "mi" {
		 = setTeXClass(, )
		// AbstractMmlBaseNode.core() is the immediate base. Its recursive
		// operator has already transferred and cleared this spacing state.
		updateTeXClass(, )
	} else {
		setTeXClass(, nil)
		 = 
	}
	for ,  := range .Children[1:] {
		setTeXClass(, nil)
	}
	return 
}

func coreNode( *mml.Node) *mml.Node {
	for  != nil && .Flags.Embellished && .Kind != "mo" {
		 := .Flags.CoreIndex
		if  < 0 ||  >= len(.Children) {
			break
		}
		 = .Children[]
	}
	return 
}

func actionSelection( *mml.Node) *mml.Node {
	if len(.Children) == 0 {
		return nil
	}
	 := 1
	if ,  := numberAttribute(, "selection");  {
		 = int()
	}
	if  < 1 ||  > len(.Children) {
		 = 1
	}
	return .Children[-1]
}

func setPreviousClass(,  *mml.Node) {
	.PrevClass = mml.TeXClassNone
	.PrevLevel = 0
	if  == nil {
		return
	}
	.PrevClass = .TeXClass
	if ,  := numberAttribute(, "scriptlevel");  {
		.PrevLevel = int()
	}
}

func updateTeXClass(,  *mml.Node) {
	if  == nil ||  == nil {
		return
	}
	.PrevClass = .PrevClass
	.PrevLevel = .PrevLevel
	.PrevClass = mml.TeXClassNone
	.PrevLevel = 0
	.TeXClass = .TeXClass
}

func adjustTeXClass(,  *mml.Node) *mml.Node {
	 := effectiveTeXClass()
	if  == mml.TeXClassNone {
		return 
	}
	 := mml.TeXClassNone
	if  != nil {
		// MmlMo.adjustTeXclass uses prev.texClass || ORD. Unset classes
		// (for example mfrac's null class) differ from explicit NONE.
		 = effectiveTeXClass()
		if ,  := .Property("autoOP");  && truthy() &&
			( == mml.TeXClassBin ||  == mml.TeXClassRel) {
			.TeXClass = mml.TeXClassOrd
			 = mml.TeXClassOrd
		}
	}
	.PrevClass = 
	if  != nil {
		// MmlMo (also used by TeXAtom) reads the current inherited level,
		// excluding explicit attributes. Without a predecessor it retains
		// the previous level already stored on this node.
		.PrevLevel = 0
		if ,  := .Attributes.GetInherited("scriptlevel");  {
			switch level := .(type) {
			case int:
				.PrevLevel = 
			case int64:
				.PrevLevel = int()
			case float64:
				.PrevLevel = int()
			}
		}
	}

	if  == mml.TeXClassBin &&
		( == mml.TeXClassNone ||  == mml.TeXClassBin ||
			 == mml.TeXClassOp ||  == mml.TeXClassRel ||
			 == mml.TeXClassOpen ||  == mml.TeXClassPunct) {
		.TeXClass = mml.TeXClassOrd
		 = mml.TeXClassOrd
	} else if  == mml.TeXClassBin &&
		( == mml.TeXClassRel ||  == mml.TeXClassClose ||  == mml.TeXClassPunct) {
		.TeXClass = mml.TeXClassOrd
		.PrevClass = mml.TeXClassOrd
	} else if  == mml.TeXClassBin && isLastTeXAtom() {
		.TeXClass = mml.TeXClassOrd
	}
	return 
}

func isLastTeXAtom( *mml.Node) bool {
	 := 
	 := .Parent
	for  != nil && .Parent != nil && .Flags.Embellished &&
		(len(.Children) == 1 || (.Kind != "mrow" && .Core() == )) {
		 = 
		 = .Parent
	}
	return  != nil && len(.Children) != 0 && .Children[len(.Children)-1] == 
}

func effectiveTeXClass( *mml.Node) mml.TeXClass {
	if  == nil {
		return mml.TeXClassNone
	}
	// TeXAtom has a concrete class field even when it is explicitly NONE.
	// Its serialization property is independent and cannot override the field.
	if .Kind == "TeXAtom" || .TeXClass != mml.TeXClassNone {
		return .TeXClass
	}
	// MmlNode.texClass can be supplied as an internal property on any node,
	// not only TeXAtom.  The invisible ApplyFunction mo uses an explicit -1;
	// preserving it prevents spurious OP-to-ORD spacing.
	if ,  := .Property("texClass");  {
		switch class := .(type) {
		case mml.TeXClass:
			return 
		case int:
			return mml.TeXClass()
		case int64:
			return mml.TeXClass()
		case float64:
			return mml.TeXClass(int())
		}
	}
	switch .Kind {
	case "text", "mspace", "mprescripts", "none", "annotation", "annotation-xml", "XML", "xml":
		return mml.TeXClassNone
	default:
		// AbstractMmlNode.texSpacing() treats a null texClass as ORD via
		// `this.texClass || TEXCLASS.ORD`.
		return mml.TeXClassOrd
	}
}