// Copyright (c) 2021-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/empheq/EmpheqConfiguration.ts and EmpheqUtil.ts.

package tex

import (
	
	

	
)

// empheqMO contains the fixed argument passed to EmpheqMethods.EmpheqMO by
// the empheq-macros CommandMap.  The "big" spellings deliberately create the
// same <mo> as their non-big counterparts, exactly as in the upstream map.
var empheqMO = map[string]string{
	"empheqlbrace": "{", "empheqrbrace": "}",
	"empheqlbrack": "[", "empheqrbrack": "]",
	"empheqlangle": "⟨", "empheqrangle": "⟩",
	"empheqlparen": "(", "empheqrparen": ")",
	"empheqlvert": "|", "empheqrvert": "|",
	"empheqlVert": "‖", "empheqrVert": "‖",
	"empheqlfloor": "⌊", "empheqrfloor": "⌋",
	"empheqlceil": "⌈", "empheqrceil": "⌉",

	"empheqbiglbrace": "{", "empheqbigrbrace": "}",
	"empheqbiglbrack": "[", "empheqbigrbrack": "]",
	"empheqbiglangle": "⟨", "empheqbigrangle": "⟩",
	"empheqbiglparen": "(", "empheqbigrparen": ")",
	"empheqbiglvert": "|", "empheqbigrvert": "|",
	"empheqbiglVert": "‖", "empheqbigrVert": "‖",
	"empheqbiglfloor": "⌊", "empheqbigrfloor": "⌋",
	"empheqbiglceil": "⌈", "empheqbigrceil": "⌉",
}

var empheqDelimiters = map[string]bool{
	"empheql": true, "empheqr": true,
	"empheqbigl": true, "empheqbigr": true,
}

// empheqCommand is the single command-dispatch hook for Empheq's delimiter
// helpers.  It must run before the generic source-symbol fallback.
func ( *parser) ( string) ( []*mml.Node,  bool,  error) {
	if ,  := empheqMO[];  {
		return []*mml.Node{.token("mo", )}, true, nil
	}
	if !empheqDelimiters[] {
		return nil, false, nil
	}
	,  := .readDelimiter(, false)
	if  != nil {
		// TexParser.GetDelimiter reports the calling control sequence, rather
		// than the invalid delimiter token, in this diagnostic.
		if ,  := .(*Error);  && .ID == "MissingOrUnrecognizedDelim" {
			 = texError("MissingOrUnrecognizedDelim", "Missing or unrecognized delimiter for \\%s", )
		}
		return nil, true, 
	}
	 := .token("mo", )
	.Attributes.Set("stretchy", true)
	.Attributes.Set("symmetric", true)
	return []*mml.Node{}, true, nil
}

// empheqEnvironment is the single begin-environment hook.  p.pos must be
// immediately after \begin{empheq}; it consumes options, the inner environment
// argument, and the matching \end{empheq}.
func ( *parser) ( string) ( []*mml.Node,  bool,  error) {
	if  != "empheq" {
		return nil, false, nil
	}

	, ,  := .readBrackets(nil)
	if  != nil {
		return nil, true, 
	}
	, ,  := .readArgument("begin{"++"}", false)
	if  != nil {
		return nil, true, 
	}
	 := strings.Split(, "=")
	 := [0]
	 := ""
	if len() > 1 {
		 = [1]
	}
	if !empheqAllowedEnvironment() {
		return nil, true, texError("UnknownEnv", "Unknown environment %q", )
	}

	 := map[string]any{}
	if  != "" {
		,  = empheqSplitOptions()
		if  != nil {
			return nil, true, 
		}
	}
	,  := .captureEnvironment()
	if  != nil {
		return nil, true, 
	}

	// The local parser has the same table structure for flalign as align but
	// does not expose flalign as a separate public environment.  Parsing it as
	// align retains Empheq's observable MML construction (including rows and
	// labels) while the Empheq adjustment below supplies its added columns.
	 := 
	if strings.TrimSuffix(, "*") == "flalign" {
		 = "align"
		if strings.HasSuffix(, "*") {
			 += "*"
		}
	}
	 := "\\begin{" +  + "}"
	if  != "" {
		 += "{" +  + "}"
	}
	 +=  + "\\end{" +  + "}"
	,  := .parseString()
	if  != nil {
		return nil, true, 
	}

	,  := ["left"]
	,  := ["right"]
	if ( && empheqTruthy()) || ( && empheqTruthy()) {
		 := .copyNode()
		if  && empheqTruthy() {
			if  := .empheqAddLeft(, , empheqOptionTeX());  != nil {
				return nil, true, 
			}
		}
		if  && empheqTruthy() {
			if  := .empheqAddRight(, , empheqOptionTeX());  != nil {
				return nil, true, 
			}
		}
	}
	return []*mml.Node{}, true, nil
}

func empheqAllowedEnvironment( string) bool {
	switch strings.TrimSuffix(, "*") {
	case "equation", "align", "gather", "flalign", "alignat", "multline":
		return true
	}
	return false
}

func empheqTruthy( any) bool {
	switch value := .(type) {
	case bool:
		return 
	case string:
		return  != ""
	}
	return  != nil
}

func empheqOptionTeX( any) string {
	if ,  := .(string);  {
		return 
	}
	// readKeyval's bare-key and empty-braces forms produce boolean true.
	// Passing it to TexParser in JavaScript yields an empty parse (the boolean
	// has no string length), leaving only the sizing phantom in the cell.
	return ""
}

// empheqSplitOptions is ParseUtil.keyvalOptions(text, {left: 1, right: 1},
// true).  readKeyval trims outer spaces, strips balanced outer braces,
// converts the two boolean literals, and treats a bare key as true.
func empheqSplitOptions( string) (map[string]any, error) {
	,  := parseUtilKeyvalOptions(, func( string) bool {
		return  == "left" ||  == "right"
	}, true)
	if  != nil {
		return nil, 
	}
	 := make(map[string]any, .Len())
	.Range(func( string,  any) bool {
		[] = 
		return true
	})
	return , nil
}

func empheqReadOptionValue(,  string) ( string,  byte,  string,  error) {
	 := 0
	 := 0
	 := true
	 := false
	var  strings.Builder
	for  := 0;  < len(); ++ {
		 := []
		switch  {
		case ' ':
			// Spaces do not affect brace-state tracking.  They remain in the
			// collected value and are removed only at its outer edges below.
		case '{':
			if  {
				++
			} else {
				 = false
				if  >  {
					 = 
				}
			}
			++
		case '}':
			if  > 0 {
				--
			}
			if  ||  {
				--
				 = true
			}
			 = false
		default:
			if  == 0 && strings.IndexByte(, ) >= 0 {
				 := empheqRemoveOptionBraces(.String(), )
				if  {
					 = "true"
				}
				return , , [+1:], nil
			}
			 = false
			 = false
		}
		.WriteByte()
	}
	if  != 0 {
		return "", 0, "", texError("ExtraOpenMissingClose", "Extra open brace or missing close brace")
	}
	 := empheqRemoveOptionBraces(.String(), )
	if  {
		 = "true"
	}
	return , 0, "", nil
}

func empheqRemoveOptionBraces( string,  int) string {
	for  > 0 {
		 = strings.TrimFunc(, internalTextSpace)
		if len() >= 2 {
			 = [1 : len()-1]
		}
		--
	}
	return strings.TrimFunc(, internalTextSpace)
}

func empheqColumnCount( *mml.Node) int {
	 := 0
	for ,  := range .Children {
		 := len(.Children)
		if .Kind == "mlabeledtr" {
			--
		}
		if  >  {
			 = 
		}
	}
	return 
}

func ( *parser) ( string,  *mml.Node,  ...string) (*mml.Node, error) {
	 := node("mpadded")
	.Attributes.Set("height", 0)
	.Attributes.Set("depth", 0)
	.Attributes.Set("voffset", "-1height")
	,  := .parseChild()
	if  != nil {
		return nil, 
	}
	if len() != 0 && [0] != "" {
		 := .amsTags()
		if .current.labelID != "" {
			.current.environment = [0]
			// D2 uses NoTags, whose getTag override ignores the force flag.
			if ,  := .getTag();  != nil {
				return nil, 
			}
		}
	}
	 := []*mml.Node{}
	if .Kind == "mrow" && .Flags.Inferred {
		 = .Children
	}
	for ,  := range  {
		.Children[0].AppendChild()
	}
	 := node("mpadded", )
	.Attributes.Set("width", 0)
	.Children[0].AppendChild(node("mphantom", ))
	refreshDynamicFlags(.Children[0])
	refreshDynamicFlags()
	return , nil
}

func empheqTopRowTable( *mml.Node) *mml.Node {
	return empheqCopiedTopRowTable(.Clone())
}

func empheqCopiedTopRowTable( *mml.Node) *mml.Node {
	if .Flags.Arity < 0 && len(.Children) == 1 && .Children[0].Flags.Inferred {
		// Source setChildren(childNodes.slice(0, 1)) delegates to this
		// inferred row. It clears the row before appending that same row,
		// whose now-empty contents are flattened by appendChild.
		.Children[0].SetChildren(nil)
		refreshDynamicFlags(.Children[0])
		refreshDynamicFlags()
	} else if len(.Children) > 1 {
		.SetChildren(.Children[:1])
	}
	.Attributes.Set("align", "baseline 1")
	 := node("mpadded", )
	.Attributes.Set("width", 0)
	return node("mphantom", )
}

func ( *parser) ( *mml.Node,  string,  *mml.Node,  ...string) error {
	,  := .empheqCellBlock(, .copyNode(), ...)
	if  != nil {
		return 
	}
	 := node("mpadded", , .empheqTopRowTable())
	.Attributes.Set("height", 0)
	.Attributes.Set("depth", 0)
	.Attributes.Set("voffset", "height")
	.Children[0].AppendChild()
	refreshDynamicFlags(.Children[0])
	refreshDynamicFlags()
	return nil
}

func ( *parser) (,  *mml.Node,  string,  ...string) error {
	,  := .Attributes.Get("columnalign")
	,  := .Attributes.Get("columnspacing")
	.Attributes.Set("columnalign", "right "+sourceValueString())
	.Attributes.Set("columnspacing", "0em "+sourceValueString())
	var  *mml.Node
	for  := len(.Children) - 1;  >= 0; -- {
		 := .Children[]
		 := node("mtd")
		 := append([]*mml.Node{}, .Children...)
		if .Kind == "mlabeledtr" && len() > 1 {
			[0], [1] = [1], [0]
		}
		.SetChildren()
		 = 
	}
	if  == nil {
		// Original EmpheqUtil dereferences an absent mtd here. Preserve
		// that unusable conversion as an error rather than a Go panic.
		return fmt.Errorf("Empheq left block requires a table row")
	}
	return .empheqRowspanCell(, , , ...)
}

func ( *parser) (,  *mml.Node,  string) error {
	if len(.Children) == 0 {
		.AppendChild(node("mtr"))
	}
	 := empheqColumnCount()
	 := .Children[0]
	for len(.Children) <  {
		.AppendChild(node("mtd"))
	}
	 := .AppendChild(node("mtd"))
	if  := .empheqRowspanCell(, , );  != nil {
		return 
	}
	,  := .Attributes.Get("columnalign")
	 := strings.Split(sourceValueString(), " ")
	if  < len() {
		 = [:]
	}
	.Attributes.Set("columnalign", strings.Join(, " ")+" left")
	,  := .Attributes.Get("columnspacing")
	 := strings.Split(sourceValueString(), " ")
	 :=  - 1
	if  < 0 {
		 = 0
	}
	if  < len() {
		 = [:]
	}
	.Attributes.Set("columnspacing", strings.Join(, " ")+" 0em")
	return nil
}