// Copyright (c) 2009-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/input/tex/ParseUtil.ts, base/{BaseItems,BaseMethods}.ts,
// and ams/{AmsMethods,AmsItems,AmsMappings}.ts.

package tex

import (
	

	
)

// baseAMSCommand is the narrow hook for the remaining source methods whose
// stack behavior is not represented by the legacy direct handlers.
func ( *parser) ( string) ( []*mml.Node,  bool,  error) {
	if ,  := amsArrowSizes[];  {
		,  = .amsXArrow()
		return , true, 
	}
	switch  {
	case "genfrac":
		,  = .amsGenfrac()
	case "DeclareMathOperator":
		 = .amsDeclareMathOperator()
	case "operatorname":
		,  = .amsOperatorName()
	case "idotsint":
		,  = .amsMultiIntegral()
	case "sideset":
		,  = .amsSideSet()
	default:
		return nil, false, nil
	}
	return , true, 
}

// baseAMSEnvironment owns the common FlalignItem row and table policy.
func ( *parser) ( string) ( []*mml.Node,  bool,  error) {
	if  != "flalign" &&  != "flalign*" && !isAMSXAlignAt() {
		return nil, false, nil
	}
	,  = .amsFlalignEnvironment()
	return , true, 
}

func ( *parser) ( string) ([]*mml.Node, error) {
	,  := .amsGenfracDelimiter()
	if  != nil {
		return nil, 
	}
	,  := .amsGenfracDelimiter()
	if  != nil {
		return nil, 
	}
	, ,  := .readArgument(, false)
	if  != nil {
		return nil, 
	}
	, ,  := .readArgument(, false)
	if  != nil {
		return nil, 
	}
	 = amsGenfracTrimStyle()
	,  := .parseArgument()
	if  != nil {
		return nil, 
	}
	,  := .parseArgument()
	if  != nil {
		return nil, 
	}
	 := node("mfrac", , )
	if  != "" {
		.Attributes.Set("linethickness", )
	}
	var  *mml.Node = 
	if  != nil ||  != nil {
		.SetProperty("withDelims", true)
		,  = .amsGenfracFixedFence(, , )
		if  != nil {
			return nil, 
		}
	}
	if  != "" {
		,  := amsGenfracStyleIndex()
		if ! {
			return nil, texError("BadMathStyleFor", "Bad math style for \\%s", )
		}
		 := map[string]any{}
		if  == 0 {
			["displaystyle"] = true
			["scriptlevel"] = 0
		} else {
			["displaystyle"] = false
			["scriptlevel"] =  - 1
		}
		 := node("mstyle", )
		.Attributes.Set("displaystyle", ["displaystyle"])
		.Attributes.Set("scriptlevel", ["scriptlevel"])
		 = 
	}
	return []*mml.Node{}, nil
}

// Genfrac reads its style with ParseUtil.trimSpaces, including the one-space
// restoration for a terminal backslash followed by an original ASCII space.
func amsGenfracTrimStyle( string) string {
	 := strings.TrimFunc(, internalTextSpace)
	if strings.HasSuffix(, "\\") && strings.HasSuffix(, " ") {
		 += " "
	}
	return 
}

// Genfrac only observes whether parseInt(style, 10) selects an index 0..3.
// Keep that decision independent of machine integer width: an accumulated
// value above 3 cannot return to range as further decimal digits are read.
func amsGenfracStyleIndex( string) (int, bool) {
	 := 0
	 := false
	if len() != 0 && ([0] == '+' || [0] == '-') {
		 = [0] == '-'
		++
	}
	 := 
	 := 0
	for  < len() && [] >= '0' && [] <= '9' {
		 = *10 + int([]-'0')
		if  > 3 {
			return 0, false
		}
		++
	}
	if  ==  || ( &&  != 0) {
		return 0, false
	}
	return , true
}

// amsFixedFencePalette ports ParseUtil.mathPalette.  MathChoice selects bigg
// in display style and big in text, script, and scriptscript styles during
// the inherited-attribute pass.
func amsFixedFencePalette( string,  mml.TeXClass) *mml.Node {
	return amsFixedFencePaletteWithToken(, , token)
}

func amsFixedFencePaletteWithToken( string,  mml.TeXClass,  func(string, string) *mml.Node) *mml.Node {
	 := amsFixedFenceWithToken(, , "2.047em", )
	 := amsFixedFenceWithToken(, , "1.2em", )
	 := amsFixedFenceWithToken(, , "1.2em", )
	 := amsFixedFenceWithToken(, , "1.2em", )
	return node("MathChoice", , , , )
}

// amsFixedFence ports BaseMethods.MakeBig.  The sizes preserve JavaScript's
// source truncation after multiplying by P_HEIGHT (1.2/.85).
func amsFixedFence( string,  mml.TeXClass,  string) *mml.Node {
	return amsFixedFenceWithToken(, , , token)
}

func amsFixedFenceWithToken( string,  mml.TeXClass,  string,  func(string, string) *mml.Node) *mml.Node {
	 := ("mo", )
	.Attributes.Set("minsize", )
	.Attributes.Set("maxsize", )
	.Attributes.Set("fence", true)
	.Attributes.Set("stretchy", true)
	.Attributes.Set("symmetric", true)
	return texAtom(, )
}

func ( *parser) ( string) error {
	 := .readStar()
	, ,  := .readArgument(, false)
	if  != nil {
		return 
	}
	// HandleDeclareOp trims the argument and removes an optional backslash;
	// unlike GetCsNameArgument, it does not validate the resulting name.
	 := strings.TrimFunc(, internalTextSpace)
	if strings.HasSuffix(, "\\") && strings.HasSuffix(, " ") {
		 += " " // ParseUtil.trimSpaces preserves a terminal control-space.
	}
	 = strings.TrimPrefix(, "\\")
	, ,  := .readArgument(, false)
	if  != nil {
		return 
	}
	 := "\\operatorname"
	if  {
		 += "*"
	}
	.state.macros[] = macroDefinition{body:  + "{" +  + "}"}
	return nil
}

func ( *parser) ( string) ([]*mml.Node, error) {
	 := .readStar()
	, ,  := .readArgument(, false)
	if  != nil {
		return nil, 
	}
	// HandleOperatorName uses ParseUtil.trimSpaces, including its final
	// control-space exception, before creating the child parser.
	 := strings.TrimFunc(, internalTextSpace)
	if strings.HasSuffix(, "\\") && strings.HasSuffix(, " ") {
		 += " "
	}
	// HandleOperatorName creates one child parser with a copied environment.
	// Its regex and font replace the caller's choices; noAutoOP is inherited.
	 := *
	.activeFont = "normal"
	.fontExplicitEmpty = false
	.identifierPattern = identifierPatternOperator
	.operatorLetters = true
	,  := .parseChild()
	if  != nil {
		return nil, 
	}
	if .Kind != "mi" {
		 = node("TeXAtom", )
	}
	// HandleOperatorName reparses in an explicit normal-font environment.
	// That environment suppresses Physics' vector token factory, including
	// for fixed-symbol tokens whose own variant is not replaced by normal.
	.Walk(func( *mml.Node) bool {
		if ,  := .Property(vectorFactoryToken);  == true {
			.SetProperty(vectorFactoryDone, true)
		}
		return true
	})
	.TeXClass = mml.TeXClassOp
	// The primary TeXAtom constructor owns texClass before the final writes.
	// A singular mi receives those properties in the handler's source order.
	if .Kind != "mi" {
		.SetProperty("texClass", mml.TeXClassOp)
	}
	.SetProperty("movesupsub", )
	.SetProperty("movablelimits", true)
	.SetProperty("texClass", mml.TeXClassOp)
	if ! {
		// GetNext consumes JavaScript whitespace even when no limits follows.
		for .pos < len(.source) && internalTextSpace(.peekRune()) {
			.consumeRune()
		}
		 := .pos
		if .pos < len(.source) && .source[.pos] == '\\' {
			.pos++
			if .readControlSequence() != "limits" {
				.pos = 
			}
		}
	}
	return []*mml.Node{}, nil
}

func ( *parser) ( string) ([]*mml.Node, error) {
	return .parseContinuationExpansion("\\int\\cdots\\int")
}

func ( *parser) ( string) ([]*mml.Node, error) {
	,  := .parseSideSetArgument()
	if  != nil {
		return nil, 
	}
	,  := splitSideSet()
	,  := .parseSideSetArgument()
	if  != nil {
		return nil, 
	}
	,  := splitSideSet()
	,  := .parseSideSetArgument()
	if  != nil {
		return nil, 
	}
	 := 
	if  != nil {
		if  != nil {
			// Copy only at the original phantom-construction point, after
			// all three arguments have registered their creation events.
			 := node("mpadded", .copyNode())
			.Attributes.Set("width", 0)
			 := node("mphantom", )
			if  := .ReplaceChild(, .Children[0]);  != nil {
				return nil, 
			}
			refreshDynamicFlags()
		} else {
			 := []*mml.Node{}
			if  != nil {
				 = append(, sideSetScript(, 1), sideSetScript(, 2))
			}
			 = append(, node("mprescripts"), sideSetScript(, 1), sideSetScript(, 2))
			 = node("mmultiscripts", ...)
			.SetProperty("scriptalign", "left")
		}
	}
	if  != nil &&  ==  {
		if  := .ReplaceChild(, .Children[0]);  != nil {
			return nil, 
		}
		refreshDynamicFlags()
		 = 
	}

	// TeXAtom.appendChild appends into its inferred row and flattens an
	// inferred child there. Preserve explicit groups and reuse actual nodes.
	var  []*mml.Node
	 := func( *mml.Node) {
		if  == nil {
			return
		}
		if .Flags.Inferred {
			 = append(, .Children...)
		} else {
			 = append(, )
		}
	}
	if  != nil {
		()
		()
	}
	()
	()
	 := texAtom(forcedRow(, true), mml.TeXClassOp)
	.SetProperty("movesupsub", true)
	.SetProperty("movablelimits", true)
	return []*mml.Node{}, nil
}

func ( *parser) ( string) (*mml.Node, error) {
	, ,  := .readArgument(, false)
	if  != nil {
		return nil, 
	}
	return .parseChild()
}

// splitSideSet removes only the source's qualifying leading node. In the
// inferred-row branch the source checks its empty mi base, not its family.
// Keep the same non-nil rest row even if removing that child leaves it empty.
func splitSideSet( *mml.Node) (,  *mml.Node) {
	if  == nil || .Flags.Inferred && len(.Children) == 0 {
		return nil, nil
	}
	if (.Kind == "msubsup" || .Kind == "msub" || .Kind == "msup" || .Kind == "mmultiscripts") && sideSetEmptyBase() {
		return , nil
	}
	if !.Flags.Inferred || len(.Children) == 0 || !sideSetEmptyBase(.Children[0]) {
		return nil, 
	}
	 = .Children[0]
	// The source splices the slot without clearing the removed child's
	// parent. Later constructor delivery reparents the actual node.
	.Children = .Children[1:]
	refreshDynamicFlags()
	return , 
}

func sideSetEmptyBase( *mml.Node) bool {
	return  != nil && len(.Children) != 0 && .Children[0] != nil &&
		.Children[0].Kind == "mi" && textContent(.Children[0]) == ""
}

// SideSet reads literal slots 1 and 2. Only the parser's marked eager msup
// represents the source's still-generic [base, nil, sup] at this boundary.
// Genuine PrimeItem msup keeps its literal child1, as in the original method.
func sideSetScript( *mml.Node,  int) *mml.Node {
	if ,  := .Property(limitsScriptOrigin);  == true && .Kind == "msup" {
		if  == 1 {
			return node("none")
		}
		 = 1
	}
	if  < len(.Children) && .Children[] != nil {
		return .Children[]
	}
	return node("none")
}

type amsArrowSize struct {
	character string
	left      float64
	right     float64
}

// Source command maps are ordered by the active package registrations.
// Mhchem's later xArrow entries replace Mathtools padding and add aliases.
var amsArrowSizes = func() map[string]amsArrowSize {
	 := make(map[string]amsArrowSize)
	for ,  := range mjSourceMaps {
		if .Kind != mjSourceCommandMap {
			continue
		}
		for ,  := range .Entries {
			, ,  := sourceHandler(.Value)
			if ! ||  != "xArrow" || len() != 3 {
				continue
			}
			,  := sourceInt([0])
			,  := sourceInt([1])
			,  := sourceInt([2])
			if  &&  &&  {
				[.Name] = amsArrowSize{string(rune()), float64(), float64()}
			}
		}
	}
	return 
}()

func ( *parser) ( string) ([]*mml.Node, error) {
	, ,  := .readBrackets(nil)
	if  != nil {
		// The source GetBrackets diagnostic names the invoked command.
		if ,  := .(*Error);  && .ID == "MissingCloseBracket" {
			return nil, texError(.ID, "Could not find closing ']' for argument to \\%s", )
		}
		return nil, 
	}
	,  := .parseArgument()
	if  != nil {
		return nil, 
	}
	 := amsArrowSizes[]
	 := .operator(.character, mml.TeXClassRel, nil)
	.Attributes.Set("stretchy", true)
	 := node("mstyle", )
	.Attributes.Set("scriptlevel", 0)
	 := append(unwrapInferred(), amsArrowStrut("depth", ".25em"))
	 := node("mpadded", ...)
	amsSetArrowPadding(, )
	.Attributes.Set("voffset", "-.2em")
	.Attributes.Set("height", "-.2em")
	if  == "" {
		 := node("mover", , )
		.SetProperty("subsupOK", true)
		return []*mml.Node{}, nil
	}
	,  := .parseArgumentString()
	if  != nil {
		return nil, 
	}
	 := append(unwrapInferred(), amsArrowStrut("height", ".75em"))
	 := node("mpadded", ...)
	amsSetArrowPadding(, )
	.Attributes.Set("voffset", ".15em")
	.Attributes.Set("depth", "-.15em")
	 := node("munderover", , , )
	.SetProperty("subsupOK", true)
	return []*mml.Node{}, nil
}

func amsArrowStrut(,  string) *mml.Node {
	 := node("mspace")
	.Attributes.Set(, )
	return 
}

func amsSetArrowPadding( *mml.Node,  amsArrowSize) {
	.Attributes.Set("width", "+"+emLength((.left+.right)/18))
	.Attributes.Set("lspace", emLength(.left/18))
}