// Copyright (c) 2009-2022 The MathJax Consortium
// SPDX-License-Identifier: Apache-2.0
// Source: ts/input/tex/base/{BaseMethods,BaseItems}.ts.

package tex

import (
	

	
	
)

func ( *parser) ( string) ([]*mml.Node, error) {
	 :=  == "eqalignno" ||  == "leqalignno"
	 :=  == "eqalign" || 
	 :=  == "displaylines"
	var  string
	var  error
	if  {
		// Matrix installs a required-close Array at the current cursor.
		// Run macros before deciding which close reaches that Array.
		 = .startMatrixBody()
	} else {
		,  = .readMatrixBody()
	}
	if  != nil {
		return nil, 
	}
	 := node("mtable")
	var  *ordinaryArrayItem
	if  {
		// Standalone Matrix has no Begin to continue after a direct Pop.
		 = &ordinaryArrayItem{}
	}
	var  []*mml.Node
	 := &arrayRowSpacing{}
	 := arrayBodyOwner{
		live:         ,
		ordinary:     ,
		rules:        newArrayRules(),
		requireClose: true,
		hasEntries:   func() bool { return len() != 0 },
		endEntry: func( []*mml.Node,  *arrayCellState) error {
			 := .finish(matrixCellContent(), len())
			 = append(, arrayCellNode())
			return nil
		},
		endRow: func() error {
			 := node("mtr", ...)
			if  && len() == 3 {
				 = node("mlabeledtr", [2], [0], [1])
			}
			.AppendChild()
			 = nil
			.rows++
			return nil
		},
		addSpacing: .add,
	}
	if  == "cases" {
		.prepare = func( *parser) ([]*mml.Node, error) {
			if len() != 1 {
				return nil, nil
			}
			return .prepareMatrixCasesText()
		}
	}
	if  := .parseArrayBody(, );  != nil {
		return nil, 
	}
	if  != nil && .popped {
		return .nodes, nil
	}
	.Attributes.Set("rowspacing", "4pt")
	.Attributes.Set("columnspacing", "1em")
	if  {
		.Attributes.Set("rowspacing", ".5em")
		.Attributes.Set("columnspacing", "0.278em")
		.Attributes.Set("displaystyle", true)
		.Attributes.Set("columnalign", "right left")
	}
	if  {
		.Attributes.Set("rowspacing", ".5em")
		.Attributes.Set("displaystyle", true)
		.Attributes.Set("columnalign", "center")
	}
	if  {
		 := "right"
		if  == "leqalignno" {
			 = "left"
		}
		.Attributes.Set("side", )
	}
	if  == "cases" {
		.Attributes.Set("rowspacing", ".1em")
		.Attributes.Set("columnalign", "left left")
	}
	 := "4pt"
	if  ||  {
		 = ".5em"
	}
	if  == "cases" {
		 = ".1em"
	}
	.apply(, )
	 = finishArrayRules()
	// ArrayItem.createMml uses ParseUtil.fenced, whose open/close properties
	// classify the row as INNER and preserve spacing from its predecessor.
	if  == "pmatrix" {
		return []*mml.Node{.leftRightFenced("(", , ")", true)}, nil
	}
	if  == "cases" {
		return []*mml.Node{.leftRightFenced("{", , "", true)}, nil
	}
	return []*mml.Node{}, nil
}

// ArrayItem.addRowSpacing uses ParseUtil.dimen2em, whose TeX conversions
// differ from the SVG renderer's CSS lengths (notably ex and physical units).
func matrixDimensionEm( string) float64 {
	 := dimensionFull.FindStringSubmatch()
	if  == nil {
		return 0
	}
	,  := strconv.ParseFloat([1], 64)
	switch [2] {
	case "em":
		return 
	case "ex":
		return  * .43
	case "pt":
		return  / 10
	case "pc":
		return  * 1.2
	case "px":
		return jscompat.NumberStep(*7.2) / 72
	case "in":
		return  * 7.2
	case "cm":
		return jscompat.NumberStep(*7.2) / 2.54
	case "mm":
		return jscompat.NumberStep(*7.2) / 25.4
	case "mu":
		return  / 18
	}
	return 0
}

// Matrix consumes a braced stack item, or a single character followed by a
// synthetic close brace. It does not read an unbraced control sequence as a
// complete TeX argument.
func ( *parser) ( string) (string, error) {
	if  := .startMatrixBody();  != nil {
		return "", 
	}
	, ,  := .pos, 0, 0
	for .pos < len(.source) {
		switch .consumeRune() {
		case '%':
			.skipComment()
		case '\\':
			 := .readControlSequence()
			if  == "cases" &&  == 0 && ( == "begin" ||  == "end") {
				if , ,  := .readArgument(, false);  != nil {
					return "", 
				}
				if  == "begin" {
					++
				} else {
					--
				}
			}
		case '&':
			if  == "cases" &&  == 0 &&  == 0 {
				// Delay text errors until its first cell has been parsed.
				// Entry runs only after the preceding math reaches its &.
				, ,  := matrixCasesTextEnd(.source, .pos)
				.pos = 
			}
		case '{':
			++
		case '}':
			if  == 0 {
				return .source[ : .pos-1], nil
			}
			--
		}
	}
	return "", texError("MissingCloseBrace", "Missing close brace")
}

// Matrix reads its opening argument before pushing the open ArrayItem. Script
// parsing uses the same boundary to reject an unbraced ArrayItem immediately.
func ( *parser) ( string) error {
	.skipNextSpaces()
	if .pos == len(.source) {
		return texError("MissingArgFor", "Missing argument for \\%s", )
	}
	if .source[.pos] == '{' {
		.pos++
	} else {
		 := .consumeRune()
		.source = string() + "}" + .source[.pos:]
		.pos = 0
	}
	return nil
}

type matrixSourceRow struct {
	cells      []string
	spacing    string
	casesError error
}

func splitMatrixBody( string,  bool) ([]matrixSourceRow, error) {
	 := []matrixSourceRow{{}}
	 := &parser{source: }
	 := 
	if  {
		// Entry sees the matrix's closing brace, including the non-letter
		// after a final \cr. Keep it available to the text scanner.
		 += "}"
	}
	, ,  := 0, 0, 0
	for .pos < len() {
		 := .pos
		switch .consumeRune() {
		case '%':
			.skipComment()
		case '{':
			++
		case '}':
			--
		case '&':
			if  == 0 &&  == 0 {
				 := &[len()-1]
				.cells = append(.cells, [:])
				 = .pos
				if  {
					, ,  := matrixCasesTextEnd(, .pos)
					if  != nil {
						.casesError = 
					}
					.pos = 
				}
			}
		case '\\':
			 := .readControlSequence()
			if  != 0 {
				continue
			}
			if  == "begin" ||  == "end" {
				if , ,  := .readArgument(, false);  != nil {
					return nil, 
				}
				if  == "begin" {
					++
				} else {
					--
				}
			}
			if  != 0 ||  != "\\" &&  != "cr" {
				continue
			}
			 := &[len()-1]
			.cells = append(.cells, [:])
			if  == "\\" {
				if .pos < len() && [.pos] == '*' {
					.pos++
				}
				if .pos < len() && [.pos] == '[' {
					, ,  := .readBrackets(nil)
					if  != nil {
						return nil, 
					}
					if  != "" {
						 := dimensionFull.FindStringSubmatch()
						if  == nil {
							return nil, texError("BracketMustBeDimension", "Bracket argument to \\\\ must be a dimension")
						}
						.spacing = dimensionValue()
					}
				}
			}
			 = append(, matrixSourceRow{})
			 = .pos
		}
	}
	 := &[len()-1]
	.cells = append(.cells, [:])
	return , nil
}

func ( *parser) ( string,  byte) (*mml.Node, error) {
	 := .matrixCellParser( + string())
	// Unlike an ordinary OpenItem, Matrix's ArrayItem requires a close brace;
	// a Right/Middle closing item reaches that boundary through styles/Over.
	, ,  := .parseRowWithInfix(, false, false)
	if  != nil {
		return nil, 
	}
	return .arrayCell.finish(matrixCellContent(), len()), nil
}

func ( *parser) ( string) *parser {
	// ArrayItem resets its lexical environment at entry and after every cell.
	// Keep the configuration and logical Stack.global while starting without
	// the surrounding font, root-index, or identifier-pattern state.
	return &parser{source: , state: .state, stackGlobal: .ensureStackGlobal(), display: .display,
		liveMatrix:       .liveMatrix,
		environmentOwner: .environmentOwner, environmentRow: .environmentOwner,
		vectorFactory: .vectorFactory, genfracPalette: .genfracPalette,
		starMacroChildren: .starMacroChildren, derivativeChildren: .derivativeChildren, matrixClose: true, arrayCell: &arrayCellState{}}
}

func matrixCellContent( []*mml.Node) *mml.Node {
	 := row(, true)
	.Walk(func( *mml.Node) bool {
		if .Flags.Token {
			.SetProperty(resolvedFontScope, true)
		}
		return true
	})
	return 
}