// 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.
// Sources: ts/core/MmlTree/{MmlNode,OperatorDictionary}.ts;
// MmlNodes/{math,mathchoice,mstyle,mfrac,msqrt,mroot,msubsup,munderover,
// mmultiscripts,mtable,mtr,mi,mo,maligngroup,mn,mtext,mspace,ms,mglyph,semantics}.ts;
// and ts/input/tex/FilterUtil.ts.

package tex

import (
	
	
	
	

	
	
)

// inheritedAttribute is MathJax's AttributeList pair: the node kind that
// introduced an inheritable value and the value itself.
type inheritedAttribute struct {
	source string
	value  mml.Property
}

type inheritedAttributes = ordered.Map[inheritedAttribute]

// MmlMi.singleCharacter permits a base plus these combining marks. Go's rune
// matcher consumes a supplementary scalar as the primary's surrogate pair;
// the negated class preserves JavaScript dot's four line-terminator exclusions.
var miSingleCharacter = regexp.MustCompile("^[^\n\r\u2028\u2029][\u0300-\u036F\u1AB0-\u1ABE\u1DC0-\u1DFF\u20D0-\u20EF]*$")

// MmlMo.pseudoScripts and MmlMo.primes require the entire token to belong to
// the corresponding character family. Quote remapping is a separate property:
// it must not replace the text kept by the parser or an explicit-font renderer.
var moPseudoScripts = regexp.MustCompile("^[\"'*`\u00AA\u00B0\u00B2-\u00B4\u00B9\u00BA\u2018-\u201F\u2032-\u2037\u2057\u2070\u2071\u2074-\u207F\u2080-\u208E]+$")
var moPrimes = regexp.MustCompile("^[\"'`\u2018-\u201F]+$")
var moPrimeRemap = strings.NewReplacer(
	"\"", "\u2033", "'", "\u2032", "`", "\u2035",
	"\u2018", "\u2035", "\u2019", "\u2032", "\u201A", "\u2032", "\u201B", "\u2035",
	"\u201C", "\u2036", "\u201D", "\u2033", "\u201E", "\u2033", "\u201F", "\u2036",
)

// setMathMLInheritance ports AbstractMmlNode.setInheritedAttributes() and the
// kind-specific overrides used by the complete MML registry.  MathJax runs
// this as the setInherited post-filter after TeX parsing; default layers are
// not semantically useful without the same pass.
func setMathMLInheritance( *mml.Node,  bool) {
	setInheritedAttributes(, ordered.New[inheritedAttribute](), , 0, false)
}

// cleanMathMLAttributes ports FilterUtil.cleanAttributes, which runs directly
// after setInherited and cleanStretchy in MathJax's TeX post-filter chain.
func cleanMathMLAttributes( *mml.Node) {
	.Walk(func( *mml.Node) bool {
		if .Attributes == nil {
			return true
		}
		 := map[string]bool{}
		if ,  := .Attributes.Get("mjx-keep-attrs");  {
			for ,  := range strings.Fields(sourceValueString()) {
				[] = true
			}
		}
		.Attributes.Explicit().Delete("mjx-keep-attrs")
		for ,  := range .Attributes.ExplicitNames() {
			if [] {
				continue
			}
			,  := .Attributes.GetExplicit()
			,  := .Attributes.GetInherited()
			if  && reflect.DeepEqual(, ) {
				.Attributes.Explicit().Delete()
			}
		}
		return true
	})
}

func setInheritedAttributes( *mml.Node,  *inheritedAttributes,  bool,  int,  bool) {
	if  == nil || .Kind == "text" || .Kind == "XML" {
		return
	}
	if .Kind == "MathChoice" || .Kind == "mathchoice" {
		// MmlNodes/mathchoice.ts removes this transient node during the
		// inherited-attribute pass.  Display takes the first child; otherwise
		// text, script, and scriptscript select children 1 through 3.
		 := 0
		if ! {
			 =  + 1
			if  < 1 {
				 = 1
			} else if  > 3 {
				 = 3
			}
		}
		var  *mml.Node
		if  < len(.Children) {
			 = .Children[]
		}
		if  == nil {
			 = node("mrow")
		}
		if  := .Parent;  != nil {
			_ = .ReplaceChild(, )
		}
		(, , , , )
		return
	}

	// MmlMtable first forces the four indent attributes into its inherited
	// layer. Parser-produced tables do not put these attributes explicitly on
	// the table, so no deletion step is needed here.
	if .Kind == "mtable" {
		for ,  := range []string{"indentalign", "indentalignfirst", "indentshift", "indentshiftfirst"} {
			if ,  := .Get();  {
				.Attributes.SetInherited(, .value)
			}
		}
	}

	 := .Attributes.Defaults()
	.Range(func( string,  inheritedAttribute) bool {
		if !.Has() &&  != "scriptminsize" &&  != "scriptsizemultiplier" {
			return true
		}
		if !blocksInheritance(.source, .Kind, ) {
			.Attributes.SetInherited(, .value)
		}
		return true
	})
	if ,  := .Attributes.GetExplicit("displaystyle"); ! {
		.Attributes.SetInherited("displaystyle", )
	}
	if ,  := .Attributes.GetExplicit("scriptlevel"); ! {
		.Attributes.SetInherited("scriptlevel", )
	}
	if  {
		.SetProperty("texprimestyle", true)
	}

	setChildInheritedAttributes(, , , , )
	refreshDynamicFlags()
}

func blocksInheritance(, ,  string) bool {
	switch  {
	case "mstyle":
		if  == "mpadded" {
			switch  {
			case "width", "height", "depth", "lspace", "voffset":
				return true
			}
		}
		if  == "mtable" {
			switch  {
			case "width", "height", "depth", "align":
				return true
			}
		}
	case "maligngroup":
		if ( == "mrow" ||  == "mtable") &&  == "groupalign" {
			return true
		}
	}
	return false
}

func addInheritedAttributes( *inheritedAttributes,  string,  *ordered.Map[mml.Property]) *inheritedAttributes {
	 := .Clone()
	.Range(func( string,  mml.Property) bool {
		if  != "displaystyle" &&  != "scriptlevel" &&  != "style" {
			.Set(, inheritedAttribute{source: , value: })
		}
		return true
	})
	return 
}

func addInheritedValues( *inheritedAttributes,  string,  ...any) *inheritedAttributes {
	 := .Clone()
	for  := 0;  < len();  += 2 {
		.Set([].(string), inheritedAttribute{source: , value: [+1]})
	}
	return 
}

func setChildInheritedAttributes( *mml.Node,  *inheritedAttributes,  bool,  int,  bool) {
	switch .Kind {
	case "mfenced":
		// MmlMfenced's fake operators receive the incoming context before the
		// authored children do. Their parent's explicit level/font/size can
		// differ from this context and must not replace it.
		texMMLFactory.CreateFencedNodes()
		for ,  := range append([]*mml.Node{.Fenced.Open, .Fenced.Close}, .Fenced.Separators...) {
			setInheritedAttributes(, , , , )
		}

	case "annotation", "annotation-xml":
		// MmlAnnotationXML stops child inheritance; MmlAnnotation inherits
		// that no-op. The annotation itself still receives its attributes.
		return

	case "math":
		 = addInheritedAttributes(, .Kind, .Attributes.Explicit())
		,  := .Attributes.Get("displaystyle")
		,  := .Attributes.Get("display")
		 = propertyBool() || (!propertyBool() &&  == "block")
		.Attributes.SetInherited("displaystyle", )
		if ,  := propertyIntValue(.Attributes, "scriptlevel");  {
			 = 
		} else {
			 = 0
		}

	case "mstyle":
		if ,  := .Attributes.GetExplicit("scriptlevel");  &&  != nil {
			if ,  := .(string);  {
				 = strings.TrimSpace()
				,  := strconv.Atoi()
				if  == nil {
					if strings.HasPrefix(, "+") || strings.HasPrefix(, "-") {
						 += 
					} else {
						 = 
					}
				}
			} else if ,  := propertyInt();  {
				 = 
			}
			 = false
		}
		if ,  := .Attributes.GetExplicit("displaystyle");  &&  != nil {
			 = propertyBool()
			 = false
		}
		if ,  := .Attributes.GetExplicit("data-cramped");  &&  != nil {
			 = propertyBool()
		}
		 = addInheritedAttributes(, .Kind, .Attributes.Explicit())
		if len(.Children) != 0 {
			setInheritedAttributes(.Children[0], , , , )
		}
		return

	case "mfrac":
		if ! ||  > 0 {
			++
		}
		if len(.Children) > 0 {
			setInheritedAttributes(.Children[0], , false, , )
		}
		if len(.Children) > 1 {
			setInheritedAttributes(.Children[1], , false, , true)
		}
		return

	case "msqrt":
		if len(.Children) != 0 {
			setInheritedAttributes(.Children[0], , , , true)
		}
		return

	case "mroot":
		if len(.Children) > 0 {
			setInheritedAttributes(.Children[0], , , , true)
		}
		if len(.Children) > 1 {
			setInheritedAttributes(.Children[1], , false, +2, )
		}
		return

	case "msub", "msup", "msubsup":
		if len(.Children) > 0 {
			setInheritedAttributes(.Children[0], , , , )
		}
		if len(.Children) > 1 {
			 := .Kind != "msup"
			setInheritedAttributes(.Children[1], , false, +1,  || )
		}
		if len(.Children) > 2 {
			setInheritedAttributes(.Children[2], , false, +1, )
		}
		return

	case "munder", "mover", "munderover":
		inheritUnderOver(, , , , )
		return

	case "mmultiscripts":
		inheritMultiscripts(, , , , )
		return

	case "mtable":
		if ,  := .Property("scriptlevel");  {
			if ,  := propertyInt();  &&  != 0 {
				 = 
			}
		}
		,  := .Attributes.GetExplicit("displaystyle")
		if  == nil {
			, _ = .Attributes.GetDefault("displaystyle")
		}
		 = propertyBool()
		,  := .Attributes.Get("columnalign")
		 = addInheritedValues(, .Kind,
			"columnalign", ,
			"rowalign", "center",
		)
		,  := .Attributes.Get("rowalign")
		 := strings.Fields(propertyString())
		,  := .Attributes.GetExplicit("data-cramped")
		 := "center"
		if ,  := .Get("rowalign");  {
			 = propertyString(.value)
		}
		for ,  := range .Children {
			if len() != 0 {
				,  = [0], [1:]
			}
			 := .Clone()
			.Set("rowalign", inheritedAttribute{source: .Kind, value: })
			setInheritedAttributes(, , , , propertyBool())
		}
		return

	case "mtr", "mlabeledtr":
		,  := .Attributes.Get("columnalign")
		 := strings.Fields(propertyString())
		if .Kind == "mlabeledtr" && .Parent != nil {
			,  := .Parent.Attributes.Get("side")
			 = append([]string{propertyString()}, ...)
		}
		,  := .Attributes.Get("rowalign")
		 = addInheritedValues(, .Kind,
			"rowalign", ,
			"columnalign", "center",
		)
		 := "center"
		for ,  := range .Children {
			if len() != 0 {
				,  = [0], [1:]
			}
			 := .Clone()
			.Set("columnalign", inheritedAttribute{source: .Kind, value: })
			setInheritedAttributes(, , , , )
		}
		return

	case "mi":
		// A base with combining marks still acquires italic as an inherited
		// value, not an explicit parser attribute (MmlMi.singleCharacter).
		if miSingleCharacter.MatchString(textContent()) && !.Has("mathvariant") {
			.Attributes.SetInherited("mathvariant", "italic")
		}
		return

	case "mo":
		applyOperatorInheritance()
		checkOperatorPseudoScript()
		checkOperatorPrimes()
		checkMathAccent()
		return

	case "mn", "mtext", "mspace", "ms", "mglyph":
		return
	}

	for ,  := range .Children {
		setInheritedAttributes(, , , , )
	}
}

// checkOperatorPseudoScript ports MmlMo.checkPseudoScripts. An embellished
// wrapper and a not-parent node do not change the operator's logical parent.
func checkOperatorPseudoScript( *mml.Node) {
	 := textContent()
	if !moPseudoScripts.MatchString() {
		return
	}
	 := 
	for  := .Parent;  != nil && .Kind != "math" && .Flags.Embellished;  = .Parent {
		 := 
		for .Kind != "mo" && .Flags.Embellished {
			 := .Core()
			if  == nil ||  ==  {
				break
			}
			 = 
		}
		if  !=  {
			break
		}
		 = 
	}
	 := .ParentNode()
	 :=  == nil || (.Kind != "msup" && .Kind != "msubsup" && .Kind != "mmultiscripts")
	.SetProperty("pseudoscript", )
	if  {
		.Attributes.SetInherited("lspace", 0)
		.Attributes.SetInherited("rspace", 0)
	}
}

// checkOperatorPrimes ports MmlMo.checkPrimes independently of script position.
func checkOperatorPrimes( *mml.Node) {
	 := textContent()
	if moPrimes.MatchString() {
		.SetProperty("primes", moPrimeRemap.Replace())
	}
}

// applyOperatorInheritance ports MmlMo.checkOperatorTable.  The form lookup is
// positional (prefix/infix/postfix), with an explicitly supplied form taking
// priority, and then falls back to the Unicode range table.
func applyOperatorInheritance( *mml.Node) {
	 := operatorForms()
	if ,  := .Attributes.GetExplicit("form");  {
		if ,  := .(string);  {
			 := []string{}
			for ,  := range  {
				if  !=  {
					 = append(, )
				}
			}
			 = 
		}
	}
	.Attributes.SetInherited("form", [0])

	 := textContent()
	if ,  := lookupOperatorDefinition(, );  {
		.OperatorLspace = float64(.Lspace+1) / 18
		.OperatorRspace = float64(.Rspace+1) / 18
		if ,  := .Property("texClass"); ! {
			.TeXClass = mml.TeXClass(.TexClass)
		}
		for ,  := range .Properties {
			.Attributes.SetInherited(.Name, .Value)
			if .Name == "stretchy" && propertyBool(.Value) {
				if ,  := .Property("fixStretchy");  {
					.Attributes.Set("stretchy", false)
				}
			}
		}
		.RemoveProperty("fixStretchy")
		return
	}

	 := []rune()
	if len() == 0 {
		return
	}
	for ,  := range mjOperatorRanges {
		 := int([0])
		if  < .First {
			break
		}
		if  <= .Last {
			for ,  := range mjOperatorMMLSpacing {
				if .TexClass == .TexClass {
					.OperatorLspace = float64(.Lspace+1) / 18
					.OperatorRspace = float64(.Rspace+1) / 18
					break
				}
			}
			if ,  := .Property("texClass"); ! {
				.TeXClass = mml.TeXClass(.TexClass)
			}
			.RemoveProperty("fixStretchy")
			return
		}
	}
	.RemoveProperty("fixStretchy")
}

// checkMathAccent follows MmlMo.checkMathAccent after operator inheritance.
// A recognized single-character script acquires the internal marker; an
// embellished base and an already-defined marker are deliberately excluded.
func checkMathAccent( *mml.Node) {
	if ,  := .Property("mathaccent");  {
		return
	}
	 := .ParentNode()
	if  == nil || (.Kind != "munder" && .Kind != "mover" && .Kind != "munderover") {
		return
	}
	if len(.Children) != 0 {
		 := .Children[0]
		if  != nil && .Flags.Embellished {
			 := 
			for .Kind != "mo" && .Flags.Embellished {
				 := .Core()
				if  == nil ||  ==  {
					break
				}
				 = 
			}
			if  ==  {
				return
			}
		}
	}
	 := []rune(textContent())
	const  = "\u00B4\u0301\u02CA\u0060\u0300\u02CB\u00A8\u0308\u007E\u0303\u02DC\u00AF\u0304\u02C9\u02D8\u0306\u02C7\u030C\u005E\u0302\u02C6\u2192\u20D7\u02D9\u0307\u02DA\u030A\u20DB\u20DC"
	if len() == 1 && strings.ContainsRune(, [0]) {
		.SetProperty("mathaccent", true)
	}
}

func lookupOperatorDefinition( string,  []string) (mjOperatorDefinition, bool) {
	for ,  := range  {
		var  []mjOperatorDictionaryEntry
		switch  {
		case "prefix":
			 = mjOperatorDictionaryPrefix
		case "postfix":
			 = mjOperatorDictionaryPostfix
		default:
			 = mjOperatorDictionaryInfix
		}
		for ,  := range  {
			if .Operator ==  {
				return .Definition, true
			}
		}
	}
	return mjOperatorDefinition{}, false
}

func operatorForms( *mml.Node) []string {
	 := 
	 := .Parent
	 := .ParentNode()
	for  != nil && .Flags.Embellished {
		 = 
		 = .Parent
		 = .ParentNode()
	}
	if  != nil && .Kind == "mrow" {
		 := make([]*mml.Node, 0, len(.Children))
		for ,  := range .Children {
			if  != nil && !.Flags.Spacelike {
				 = append(, )
			}
		}
		if len() != 1 {
			if len() != 0 && [0] ==  {
				return []string{"prefix", "infix", "postfix"}
			}
			if len() != 0 && [len()-1] ==  {
				return []string{"postfix", "infix", "prefix"}
			}
		}
	}
	return []string{"infix", "prefix", "postfix"}
}

func inheritUnderOver( *mml.Node,  *inheritedAttributes,  bool,  int,  bool) {
	if len(.Children) == 0 {
		return
	}
	 := 2
	 := 1
	if .Kind == "mover" {
		,  = 1, 2
	}
	setInheritedAttributes(.Children[0], , , ,  ||  < len(.Children))
	 := ! && effectiveCoreBool(.Children[0], "movablelimits")
	for  := 1;  < len(.Children); ++ {
		 := "accentunder"
		if  ==  {
			 = "accent"
		}
		,  := .Attributes.Get()
		 := 
		if  || !propertyBool() {
			++
		}
		 :=  ||  == 
		setInheritedAttributes(.Children[], , false, , )
		// MmlMunderover.setInheritedAccent derives only an unset/null stack
		// attribute from the embellished script's core operator. A changed
		// default requires a second pass with fresh inheritance and the
		// parent's display/prime context, after the first operator pass.
		,  := .Attributes.GetExplicit()
		 := .Children[]
		if  == nil && .Flags.Embellished {
			 := 
			for .Kind != "mo" && .Flags.Embellished {
				 := .Core()
				if  == nil ||  ==  {
					break
				}
				 = 
			}
			,  := .Attributes.Get("accent")
			.Attributes.SetInherited(, )
			,  := .Attributes.GetDefault()
			if !reflect.DeepEqual(, ) {
				,  := .Attributes.Get()
				 = 
				if  || !propertyBool() {
					++
				}
				setInheritedAttributes(, ordered.New[inheritedAttribute](), , , )
			}
		}
	}
}

func inheritMultiscripts( *mml.Node,  *inheritedAttributes,  bool,  int,  bool) {
	if len(.Children) == 0 {
		return
	}
	setInheritedAttributes(.Children[0], , , , )
	 := false
	 := 0
	for  := 1;  < len(.Children); ++ {
		 := .Children[]
		if .Kind == "mprescripts" {
			 = true
			continue
		}
		setInheritedAttributes(, , false, +1,  || %2 == 0)
		++
	}
	_ =  // structure is already normalized by the TeX parser
}

func effectiveCoreBool( *mml.Node,  string) bool {
	// MmlMunderover uses the node-specific coreMO contract. In particular,
	// a compound, non-embellished row is its own core; a leading movable
	// operator does not force the entire decoration into script style.
	 := limitsCore()
	if  == nil {
		return false
	}
	,  := .Attributes.Get()
	return limitsTruthy()
}

func propertyBool( any) bool {
	switch value := .(type) {
	case bool:
		return 
	case string:
		,  := strconv.ParseBool()
		return  == nil && 
	case int:
		return  != 0
	case float64:
		return  != 0
	}
	return false
}

func propertyString( any) string {
	if  == nil {
		return ""
	}
	if ,  := .(string);  {
		return 
	}
	return ""
}

func propertyInt( any) (int, bool) {
	switch value := .(type) {
	case int:
		return , true
	case float64:
		return int(), true
	case string:
		,  := strconv.Atoi(strings.TrimSpace())
		return ,  == nil
	}
	return 0, false
}

func propertyIntValue( *mml.Attributes,  string) (int, bool) {
	,  := .Get()
	if ! {
		return 0, false
	}
	return propertyInt()
}