// Copyright (c) 2020-2022 MathJax Consortium
// SPDX-License-Identifier: Apache-2.0
// This file is a Go translation and modification of MathJax 3.2.2.
//
// Source: ts/input/tex/mathtools/MathtoolsMethods.ts and MathtoolsUtil.ts.

package tex

import (
	
	
	

	
	
)

const (
	mathtoolsPairSeparator = "\x00mathtools-pair\x00"
	mathtoolsOptionPrefix  = "\x00mathtools-option:"
	mathtoolsTagPrefix     = "\x00mathtools-tag:"
	mathtoolsCurrentTag    = "\x00mathtools-current-tag"
	mathtoolsTagSeparator  = "\x00mathtools-tag-field\x00"
)

var mathtoolsNumber = regexp.MustCompile(`^[-+]?(?:\d+(?:\.\d*)?|\.\d+)$`)

var mathtoolsDefaults = map[string]string{
	"multlinegap":           "1em",
	"multlined-pos":         "c",
	"firstline-afterskip":   "",
	"lastline-preskip":      "",
	"smallmatrix-align":     "c",
	"shortvdotsadjustabove": ".2em",
	"shortvdotsadjustbelow": ".2em",
	"centercolon":           "false",
	"centercolon-offset":    ".04em",
	"thincolon-dx":          "-.04em",
	"thincolon-dw":          "-.08em",
	"use-unicode":           "false",
	"prescript-sub-format":  "",
	"prescript-sup-format":  "",
	"prescript-arg-format":  "",
	"allow-mathtoolsset":    "true",
}

func ( *parser) ( string) string {
	if ,  := .state.macros[mathtoolsOptionPrefix+];  {
		return .body
	}
	return mathtoolsDefaults[]
}

func ( *parser) ( string) bool {
	 := .mathtoolsOption()
	// readKeyval recognizes the two boolean literals; every nonempty string
	// left by that reader is truthy in the source option consumers.
	return  != "" &&  != "false"
}

func ( *parser) ( string) ([]*mml.Node, error) {
	 := ".1em"
	, ,  := .readBrackets(&)
	if  != nil {
		return nil, 
	}
	 := ".2em"
	, ,  := .readBrackets(&)
	if  != nil {
		return nil, 
	}
	, ,  := .readArgument(, false)
	if  != nil {
		return nil, 
	}
	,  := .parseArgumentString()
	if  != nil {
		return nil, 
	}
	,  := .parseArgumentString()
	if  != nil {
		return nil, 
	}
	 := layout.Em(layout.Length2Em(, .1, 1, 16))
	 := node("mpadded", node("mphantom", ))
	 := "bottom"
	,  := "mover", "accent"
	if  == "underbracket" {
		 = "top"
		,  = "munder", "accentunder"
	}
	.Attributes.Set("style", "border: "++" solid; border-"++": none")
	.Attributes.Set("height", )
	.Attributes.Set("depth", 0)
	 := node(, , )
	.Attributes.Set(, true)
	// ParseUtil.underOver(..., stack=true) wraps the stack as an OP atom and
	// installs both script-stacking properties on that outer TeXAtom.
	 := texAtom(, mml.TeXClassOp)
	.SetProperty("movesupsub", true)
	.SetProperty("subsupOK", true)
	return []*mml.Node{}, nil
}

func ( *parser) ( string) error {
	,  := .readCSNameArgument()
	if  != nil {
		return 
	}
	 := 1
	 := false
	, ,  := "", "#1", ""
	 := strings.Contains(, "X")
	 := strings.Contains(, "XPP")
	if  {
		// An absent count is undefined and uses PairedDelimiters' default one.
		// An explicitly empty count remains false and consumes no arguments.
		, ,  := .readBrackets(nil)
		if  != nil {
			return 
		}
		if  {
			 = 0
		}
		if  != "" {
			 = strings.TrimFunc(, internalTextSpace)
			 :=  != ""
			for ,  := range  {
				 =  &&  >= '0' &&  <= '9'
			}
			if ! {
				return texError("IllegalParamNumber", "Illegal number of parameters specified in \\%s", )
			}
			,  = parseInteger(, "IllegalMacroParam")
			if  != nil {
				return 
			}
			 =  == 0
		}
	}
	if  {
		, _,  = .readArgument(, false)
		if  != nil {
			return 
		}
	}
	, ,  := .readArgument(, false)
	if  != nil {
		return 
	}
	, ,  := .readArgument(, false)
	if  != nil {
		return 
	}
	if  {
		, _,  = .readArgument(, false)
		if  != nil {
			return 
		}
	}
	if  {
		, _,  = .readArgument(, false)
		if  != nil {
			return 
		}
	}
	.state.pairedDelimiters[] = pairedDelimiter{
		open: , close: , arguments: ,
		substituteZeroArgs: ,
		body:                + mathtoolsPairSeparator +  + mathtoolsPairSeparator + ,
	}
	return nil
}

func ( *parser) ( string,  pairedDelimiter) ([]*mml.Node, error) {
	 := strings.Split(.body, mathtoolsPairSeparator)
	, ,  := "", .body, ""
	if len() == 3 {
		, ,  = [0], [1], [2]
	}
	 := .readStar()
	 := ""
	if ! {
		var  error
		, _,  = .readBrackets(nil)
		if  != nil {
			// GetBrackets reports CommandMap's currentCS, the invoked paired name.
			if ,  := .(*Error);  && .ID == "MissingCloseBracket" {
				return nil, texError(.ID, "Could not find closing ']' for argument to \\%s", )
			}
			return nil, 
		}
	}
	, ,  := "", "", ""
	if  {
		, ,  = "\\left", "\\right", "\\middle"
	} else if  != "" {
		, ,  = +"l", +"r", 
	}
	if .arguments > 0 || .substituteZeroArgs {
		 := make([]string, .arguments)
		for  := range  {
			var  error
			[], _,  = .readArgument(, false)
			if  != nil {
				return nil, 
			}
		}
		var  error
		if ,  = substituteMacroArguments(, );  != nil {
			return nil, 
		}
		if ,  = substituteMacroArguments(, );  != nil {
			return nil, 
		}
		if ,  = substituteMacroArguments(, );  != nil {
			return nil, 
		}
	}
	 = strings.ReplaceAll(, "\\delimsize", )
	 := ""
	for ,  := range []string{, , .open, , , .close, , .source[.pos:]} {
		var  error
		,  = macroAddArgs(, , maxMacroBuffer)
		if  != nil {
			return nil, 
		}
	}
	// PairedDelimiters rewrites this caller before charging the expansion.
	// Its pending row/script items consume the resulting source directly.
	.source, .pos = , 0
	.state.macroCount++
	if .state.macroCount > maxMacros {
		return nil, texError("MaxMacroSub1", "MathJax maximum macro substitution count exceeded; is here a recursive macro call?")
	}
	return nil, nil
}

func ( *parser) (, ,  bool) *mml.Node {
	 := .token("mo", ":")
	if ! || (! && !.mathtoolsOptionBool("centercolon")) {
		return 
	}
	 := .mathtoolsOption("centercolon-offset")
	 := node("mpadded", )
	.Attributes.Set("voffset", )
	.Attributes.Set("height", "+"+)
	.Attributes.Set("depth", "-"+)
	if  {
		.Attributes.Set("width", .mathtoolsOption("thincolon-dw"))
		.Attributes.Set("lspace", .mathtoolsOption("thincolon-dx"))
	}
	return 
}

func ( *parser) ( string) ([]*mml.Node, error) {
	 := map[string][2]string{
		"coloneqq": {":=", "≔"}, "Coloneqq": {"::=", "⩴"},
		"coloneq": {":-", ""}, "Coloneq": {"::-", ""},
		"eqqcolon": {"=:", "≕"}, "Eqqcolon": {"=::", ""},
		"eqcolon": {"-:", "∹"}, "Eqcolon": {"-::", ""},
		"colonapprox": {":\\approx", ""}, "Colonapprox": {"::\\approx", ""},
		"colonsim": {":\\sim", ""}, "Colonsim": {"::\\sim", ""},
		"dblcolon": {"::", "∷"},
	}
	 := []
	if .mathtoolsOptionBool("use-unicode") && [1] != "" {
		return []*mml.Node{.operator([1], mml.TeXClassRel, nil)}, nil
	}
	 := strings.ReplaceAll([0], ":", "\\MTThinColon")
	 = strings.ReplaceAll(, "-", "\\mathrel{-}")
	return .parseContinuationExpansion("\\mathrel{" +  + "}")
}

func mathtoolsNArrow( string) *mml.Node {
	,  := "↑", ".06em"
	if  == "ndownarrow" {
		,  = "↓", ".25em"
	}
	 := node("mspace")
	.Attributes.Set("height", ".2em")
	.Attributes.Set("depth", 0)
	.Attributes.Set("width", ".4em")
	 := node("menclose", )
	.Attributes.Set("notation", "updiagonalstrike")
	.Attributes.Set("data-thickness", ".05em")
	.Attributes.Set("data-padding", 0)
	 := node("mpadded", )
	.Attributes.Set("width", 0)
	.Attributes.Set("lspace", "-.5width")
	.Attributes.Set("voffset", )
	 := node("mpadded", , node("mphantom", token("mtext", )))
	.Attributes.Set("width", 0)
	.Attributes.Set("lspace", "-.5width")
	 := node("TeXAtom", token("mtext", ), )
	.TeXClass = mml.TeXClassRel
	.SetProperty("texClass", mml.TeXClassRel)
	return 
}

func ( *parser) ( string,  bool) ([]*mml.Node, error) {
	,  := .parseArgument()
	if  != nil {
		return nil, 
	}
	,  := .parseArgument()
	if  != nil {
		return nil, 
	}
	 := append(unwrapInferred(), token("mi", ""), mathtoolsSpace("1em"))
	 := node("mstyle", ...)
	.Attributes.Set("scriptlevel", 0)
	 := append([]*mml.Node{mathtoolsSpace("1em"), token("mi", "")}, unwrapInferred()...)
	 := node("mstyle", ...)
	.Attributes.Set("scriptlevel", 0)
	 := node("mfrac", , )
	.Attributes.Set("linethickness", 0)
	.Attributes.Set("numalign", "left")
	.Attributes.Set("denomalign", "right")
	 := node("mstyle", )
	.Attributes.Set("displaystyle", )
	.Attributes.Set("scriptlevel", 0)
	return []*mml.Node{}, nil
}

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

func ( *parser) ( string) ([]*mml.Node, error) {
	, ,  := .readBrackets(nil)
	if  != nil {
		return nil, 
	}
	, ,  := .readArgument(, false)
	if  != nil {
		return nil, 
	}
	,  = mathtoolsPlusOrMinus(, )
	if  != nil {
		return nil, 
	}
	if  == "" {
		 = 
	}
	,  = mathtoolsPlusOrMinus(, )
	if  != nil {
		return nil, 
	}
	 := .token("mo", "(")
	.Attributes.Set("stretchy", false)
	 := node("mpadded", node("mphantom", ))
	.Attributes.Set("width", 0)
	.Attributes.Set("height", +"height")
	.Attributes.Set("depth", +"depth")
	return []*mml.Node{texAtom(, mml.TeXClassOrd)}, nil
}

func mathtoolsPlusOrMinus(,  string) (string, error) {
	 = strings.TrimFunc(, internalTextSpace)
	if !mathtoolsNumber.MatchString() {
		return "", texError("NotANumber", "Argument to \\%s is not a number", )
	}
	if [0] != '+' && [0] != '-' {
		 = "+" + 
	}
	return , nil
}

func ( *parser) ( string) ([]*mml.Node, error) {
	,  := .readDelimitedParameter(, "_")
	if  != nil {
		return nil, 
	}
	, ,  := .readArgument(, false)
	if  != nil {
		return nil, 
	}
	,  := .readDelimitedParameter(, "_")
	if  != nil {
		return nil, 
	}
	, ,  := .readArgument(, false)
	if  != nil {
		return nil, 
	}
	 := "\\mathop{{" +  + "}\\vphantom{{" +  + "}}}_{{" +  + "}\\vphantom{{" +  + "}}}" +
		"\\mathop{{" +  + "}\\vphantom{{" +  + "}}}_{{" +  + "}\\vphantom{{" +  + "}}}"
	,  := .parseContinuationExpansion()
	if  != nil {
		return nil, 
	}
	if len() == 2 {
		 = []*mml.Node{[0], .operator("\u2061", mml.TeXClassNone, nil), [1]}
	}
	return , nil
}

func ( *parser) ( string) error {
	if !.mathtoolsOptionBool("allow-mathtoolsset") {
		return texError("ForbiddenMathtoolsSet", "\\%s is disabled", )
	}
	, ,  := .readArgument(, false)
	if  != nil {
		return 
	}
	,  := parseUtilKeyvalOptions(, func( string) bool {
		,  := mathtoolsDefaults[]
		// Original SetOptions excludes the misspelled "pariedDelimiters".
		// The real key is accepted without rebuilding the delimiter map.
		return ( &&  != "allow-mathtoolsset") ||  == "pairedDelimiters"
	}, true)
	if  != nil {
		return 
	}
	for ,  := range .JavaScriptKeys() {
		,  := .Get()
		.state.macros[mathtoolsOptionPrefix+] = macroDefinition{body: fmt.Sprint()}
	}
	return nil
}

func ( *parser) ( string,  bool) error {
	, ,  := .readArgument(, false)
	if  != nil {
		return 
	}
	 = strings.TrimFunc(, internalTextSpace)
	if  == "" {
		return texError("InvalidTagFormID", "Tag form name can't be empty")
	}
	, ,  := .readBrackets(nil)
	if  != nil {
		return 
	}
	, ,  := .readArgument(, false)
	if  != nil {
		return 
	}
	, ,  := .readArgument(, false)
	if  != nil {
		return 
	}
	 := mathtoolsTagPrefix + 
	if ,  := .state.macros[];  && ! {
		return texError("DuplicateTagForm", "Duplicate tag form: %s", )
	}
	.state.macros[] = macroDefinition{body:  + mathtoolsTagSeparator +  + mathtoolsTagSeparator + }
	return nil
}

func ( *parser) ( string) error {
	, ,  := .readArgument(, false)
	if  != nil {
		return 
	}
	 = strings.TrimFunc(, internalTextSpace)
	if  == "" {
		delete(.state.macros, mathtoolsCurrentTag)
		return nil
	}
	,  := .state.macros[mathtoolsTagPrefix+]
	if ! {
		return texError("UndefinedTagForm", "Undefined tag form: %s", )
	}
	.state.macros[mathtoolsCurrentTag] = 
	return nil
}

// mathtoolsFormatTag applies the currently selected Mathtools tag form.  It is
// deliberately a side-effect-free helper so the central AMS tag owner can use
// it when constructing an mlabeledtr without depending on handler internals.
func ( *parser) ( string) string {
	,  := .state.macros[mathtoolsCurrentTag]
	if ! {
		return "(" +  + ")"
	}
	 := strings.Split(.body, mathtoolsTagSeparator)
	if len() != 3 {
		return "(" +  + ")"
	}
	 := 
	if [2] != "" {
		 = [2] + "{" +  + "}"
	}
	return [0] +  + [1]
}