// Copyright (c) 2015-2021 Martin Hensel
// Copyright (c) 2026 luo-studio
// Copyright (c) 2026 erweixin (upstream RaTeX)
// SPDX-License-Identifier: Apache-2.0 AND MIT
//
// Go translation and modification of mhchemparser 4.1.1. Initial Go
// translation structure adapted from github.com/Luo-Studio/go-tex (MIT).

package mhchem

import (
	
	
	
)

// MatchToken is the result of matching one mhchem transition pattern.
// Either a single string (S) or an ordered tuple of substrings (A).
type MatchToken struct {
	S string
	A []string
	// IsArray distinguishes the two variants. Using a bool keeps the
	// type comparable and lets callers branch without a tagged union.
	IsArray bool
}

func tokS( string) MatchToken        { return MatchToken{S: } }
func tokA( ...string) MatchToken { return MatchToken{A: , IsArray: true} }

// PatternHit is one successful pattern match: the captured token plus
// whatever input remained after it.
type PatternHit struct {
	Token     MatchToken
	Remainder string
}

// beg / end are simple sum-types: either a literal string or a regex.
// Empty string == "no anchor".
type beg struct {
	s  string
	re *jsRegexp
}
type end = beg

func bStr( string) beg    { return beg{s: } }
func bRe( *jsRegexp) beg { return beg{re: } }
func eStr( string) end    { return end{s: } }
func eRe( *jsRegexp) end { return end{re: } }

// prefixLen returns (len, true) if input starts with `b`. For Beg::Re
// only the leading anchored match matters.
func prefixLen( string,  beg) (int, bool) {
	if .re != nil {
		,  := .re.FindStringMatch()
		if  == nil || .Index != 0 {
			return 0, false
		}
		return .Length, true
	}
	if .s == "" {
		return 0, true
	}
	if len() >= len(.s) && [:len(.s)] == .s {
		return len(.s), true
	}
	return 0, false
}

// scanEnd advances through `input` from `start`, looking for the first
// position where `e` matches at depth-zero (i.e. with all { } balanced).
// Returns (matchStart, matchEnd) on success, or (0,0,false).
func scanEnd( string,  int,  end) (int, int, bool, error) {
	 := 
	 := 0
	for  < len() {
		 := [:]
		var  int
		var  bool
		if .re != nil {
			,  := .re.FindStringMatch()
			if  != nil && .Index == 0 {
				 = .Length
				 = true
			}
		} else if .s != "" {
			if len() >= len(.s) && [:len(.s)] == .s {
				 = len(.s)
				 = true
			}
		}
		if  &&  == 0 {
			return ,  + , true, nil
		}
		,  := utf8.DecodeRuneInString()
		if  == utf8.RuneError &&  <= 1 {
			return 0, 0, false, errMsg("invalid utf-8 in scanEnd")
		}
		if  == '{' {
			++
		} else if  == '}' {
			if  == 0 {
				return 0, 0, false, errExtraClose
			}
			--
		}
		 += 
	}
	return 0, 0, false, nil
}

// findObserveGroups is mhchemparser's findObserveGroups helper. See the
// pattern table in matchPattern for how the two-part variant is used.
func findObserveGroups(
	,  string,
	 beg,
	 string,
	 end,
	 *fogPart2,
	 bool,
) (*PatternHit, error) {
	 := 0
	if  != "" {
		,  := prefixLen(, bStr())
		if ! {
			return nil, nil
		}
		 = 
	}
	 := [:]
	,  := prefixLen(, )
	if ! {
		return nil, nil
	}
	 := 
	if  != "" {
		 = eStr()
	}
	, , ,  := scanEnd(, , )
	if  != nil {
		return nil, 
	}
	if ! {
		return nil, nil
	}
	 :=  != ""
	 := 
	if  {
		 = 
	}
	 := [:]
	 := [:]

	if  != nil {
		,  := (, .begExcl, .begIncl, .endIncl, .endExcl, nil, false)
		if  != nil {
			return nil, 
		}
		if  == nil {
			return nil, nil
		}
		if .Token.IsArray {
			return nil, errMsg("nested Fog pair")
		}
		 := .Token.S
		var  MatchToken
		if  {
			 = tokS( + )
		} else {
			 = tokA(, )
		}
		return &PatternHit{Token: , Remainder: .Remainder}, nil
	}
	return &PatternHit{Token: tokS(), Remainder: }, nil
}

type fogPart2 struct {
	begExcl string
	begIncl beg
	endIncl string
	endExcl end
}

// regexMatchToken runs the given regex against input. JavaScript's match()
// returns every capture when there are at least two, including empty captures;
// otherwise mhchemparser selects capture 1 or the full match.
func regexMatchToken( *jsRegexp,  string) (MatchToken, int, bool) {
	,  := .FindStringMatch()
	if  == nil {
		return MatchToken{}, 0, false
	}
	 := .Index + .Length
	 := .Groups()
	if len() > 2 {
		 := make([]string, len()-1)
		for  := 1;  < len(); ++ {
			[-1] = [].String()
		}
		return tokA(...), , true
	}
	if len() > 1 && [1].Length > 0 {
		return tokS([1].String()), , true
	}
	return tokS(.String()), , true
}

// Compile-once cache of the inline regex literals that some patterns
// embedded parser data.
var (
	reBuildOnce        sync.Once
	reAggOpen          *jsRegexp
	reCmdBrace         *jsRegexp
	reBeforeBrace      *jsRegexp
	reNegPow           *jsRegexp
	reSci              *jsRegexp
	reSoaRemainder     *jsRegexp
	reSoaAlt           *jsRegexp
	reAmountMain       *jsRegexp
	reAmountDollar     *jsRegexp
	reFormulaParenOnly *jsRegexp
	reFormulaMain      *jsRegexp
)

func mustCompile( string) *jsRegexp {
	,  := compileJSRegexp()
	if  != nil {
		panic(fmt.Sprintf("mhchem: pattern %q: %v", , ))
	}
	return 
}

func buildRegexes() {
	reBuildOnce.Do(func() {
		reAggOpen = mustCompile(`^\([a-z]{1,3}(?=[\),])`)
		reCmdBrace = mustCompile(`^\\[a-zA-Z]+\{`)
		reBeforeBrace = mustCompile(`^(?=\{)`)
		reNegPow = mustCompile(`^(\+\-|\+\/\-|\+|\-|\\pm\s?)?([0-9]+(?:[,.][0-9]+)?|[0-9]*(?:\.[0-9]+)?)\^([+\-]?[0-9]+|\{[+\-]?[0-9]+\})`)
		reSci = mustCompile(`^(\+\-|\+\/\-|\+|\-|\\pm\s?)?([0-9]+(?:[,.][0-9]+)?|[0-9]*(?:\.[0-9]+))?(\((?:[0-9]+(?:[,.][0-9]+)?|[0-9]*(?:\.[0-9]+))\))?(?:(?:([eE])|\s*(\*|x|\\times|×)\s*10\^)([+\-]?[0-9]+|\{[+\-]?[0-9]+\}))?`)
		reSoaRemainder = mustCompile(`^($|[\s,;\)\]\}])`)
		reSoaAlt = mustCompile(`^(?:\((?:\\ca\s?)?\$[amothc]\$\))`)
		reAmountMain = mustCompile(`^(?:(?:(?:\([+\-]?[0-9]+\/[0-9]+\)|[+\-]?(?:[0-9]+|\$[a-z]\$|[a-z])\/[0-9]+|[+\-]?[0-9]+[.,][0-9]+|[+\-]?\.[0-9]+|[+\-]?[0-9]+)(?:[a-z](?=\s*[A-Z]))?)|[+\-]?[a-z](?=\s*[A-Z])|\+(?!\s))`)
		reAmountDollar = mustCompile(`^\$(?:\(?[+\-]?(?:[0-9]*[a-z]?[+\-])?[0-9]*[a-z](?:[+\-][0-9]*[a-z]?)?\)?|\+|-)\$$`)
		reFormulaParenOnly = mustCompile(`^\([a-z]+\)$`)
		reFormulaMain = mustCompile(`^(?:[a-z]|(?:[0-9\ \+\-\,\.\(\)]+[a-z])+[0-9\ \+\-\,\.\(\)]*|(?:[a-z][0-9\ \+\-\,\.\(\)]+)+[a-z]?)$`)
	})
}

func patternNegPow( string) (*PatternHit, error) {
	buildRegexes()
	,  := reNegPow.FindStringMatch()
	if  == nil {
		return nil, nil
	}
	 := .Groups()
	 := make([]string, 0, len()-1)
	for  := 1;  < len(); ++ {
		 = append(, [].String())
	}
	 := .Index + .Length
	return &PatternHit{Token: tokA(...), Remainder: [:]}, nil
}

func patternSci( string) (*PatternHit, error) {
	buildRegexes()
	,  := reSci.FindStringMatch()
	if  == nil {
		return nil, nil
	}
	if .String() == "" {
		return nil, nil
	}
	 := .Groups()
	 := make([]string, 0, len()-1)
	for  := 1;  < len(); ++ {
		 = append(, [].String())
	}
	 := .Index + .Length
	return &PatternHit{Token: tokA(...), Remainder: [:]}, nil
}

func patternStateOfAgg( string) (*PatternHit, error) {
	buildRegexes()
	,  := findObserveGroups(, "", bRe(reAggOpen), ")", eStr(""), nil, false)
	if  != nil {
		return nil, 
	}
	if  != nil {
		,  := reSoaRemainder.FindStringMatch(.Remainder)
		if  != nil && .Index == 0 {
			return , nil
		}
	}
	,  := reSoaAlt.FindStringMatch()
	if  != nil {
		 := .Index + .Length
		return &PatternHit{Token: tokS(.String()), Remainder: [:]}, nil
	}
	return nil, nil
}

func patternAmount( string) (*PatternHit, error) {
	buildRegexes()
	if ,  := reAmountMain.FindStringMatch();  != nil {
		 := .Index + .Length
		return &PatternHit{Token: tokS(.String()), Remainder: [:]}, nil
	}
	,  := findObserveGroups(, "", bStr("$"), "$", eStr(""), nil, false)
	if  != nil ||  == nil {
		return nil, 
	}
	if .Token.IsArray {
		return nil, nil
	}
	if ,  := reAmountDollar.FindStringMatch(.Token.S);  != nil {
		return &PatternHit{Token: tokS(.Token.S), Remainder: .Remainder}, nil
	}
	return nil, nil
}

func patternFormula( string) (*PatternHit, error) {
	buildRegexes()
	if ,  := reFormulaParenOnly.FindStringMatch();  != nil {
		return nil, nil
	}
	,  := reFormulaMain.FindStringMatch()
	if  == nil {
		return nil, nil
	}
	 := .Index + .Length
	return &PatternHit{Token: tokS(.String()), Remainder: [:]}, nil
}

// matchPattern runs the named pattern against input. Mirrors upstream's
// match_pattern dispatcher. Patterns with bespoke logic are listed
// explicitly; everything else falls through to the precompiled, zero-dependency
// JavaScript-compatible regexp table loaded from data/patterns.json.
func matchPattern( *Data, ,  string) (*PatternHit, error) {
	buildRegexes()
	switch  {
	case "(-)(9)^(-9)":
		return patternNegPow()
	case "(-)(9.,9)(e)(99)":
		return patternSci()
	case "state of aggregation $":
		return patternStateOfAgg()
	case "amount", "amount2":
		return patternAmount()
	case "formula$":
		return patternFormula()
	case "^{(...)}":
		return findObserveGroups(, "^{", bStr(""), "", eStr("}"), nil, false)
	case "^($...$)":
		return findObserveGroups(, "^", bStr("$"), "$", eStr(""), nil, false)
	case "^\\x{}{}":
		return findObserveGroups(, "^", bRe(reCmdBrace), "}", eStr(""),
			&fogPart2{"", bStr("{"), "}", eStr("")}, true)
	case "^\\x{}":
		return findObserveGroups(, "^", bRe(reCmdBrace), "}", eStr(""), nil, false)
	case "_{(...)}":
		return findObserveGroups(, "_{", bStr(""), "", eStr("}"), nil, false)
	case "_($...$)":
		return findObserveGroups(, "_", bStr("$"), "$", eStr(""), nil, false)
	case "_\\x{}{}":
		return findObserveGroups(, "_", bRe(reCmdBrace), "}", eStr(""),
			&fogPart2{"", bStr("{"), "}", eStr("")}, true)
	case "_\\x{}":
		return findObserveGroups(, "_", bRe(reCmdBrace), "}", eStr(""), nil, false)
	case "{...}":
		return findObserveGroups(, "", bStr("{"), "}", eStr(""), nil, false)
	case "{(...)}":
		return findObserveGroups(, "{", bStr(""), "", eStr("}"), nil, false)
	case "$...$":
		return findObserveGroups(, "", bStr("$"), "$", eStr(""), nil, false)
	case "${(...)}$__$(...)$":
		,  := findObserveGroups(, "${", bStr(""), "", eStr("}$"), nil, false)
		if  != nil ||  != nil {
			return , 
		}
		return findObserveGroups(, "$", bStr(""), "", eStr("$"), nil, false)
	case `\bond{(...)}`:
		return findObserveGroups(, `\bond{`, bStr(""), "", eStr("}"), nil, false)
	case "[(...)]":
		return findObserveGroups(, "[", bStr(""), "", eStr("]"), nil, false)
	case `\x{}{}`:
		return findObserveGroups(, "", bRe(reCmdBrace), "}", eStr(""),
			&fogPart2{"", bStr("{"), "}", eStr("")}, true)
	case `\x{}`:
		return findObserveGroups(, "", bRe(reCmdBrace), "}", eStr(""), nil, false)
	case `\frac{(...)}`:
		return findObserveGroups(, `\frac{`, bStr(""), "", eStr("}"),
			&fogPart2{"{", bStr(""), "", eStr("}")}, false)
	case `\overset{(...)}`:
		return findObserveGroups(, `\overset{`, bStr(""), "", eStr("}"),
			&fogPart2{"{", bStr(""), "", eStr("}")}, false)
	case `\underset{(...)}`:
		return findObserveGroups(, `\underset{`, bStr(""), "", eStr("}"),
			&fogPart2{"{", bStr(""), "", eStr("}")}, false)
	case `\underbrace{(...)}`:
		return findObserveGroups(, `\underbrace{`, bStr(""), "", eStr("}_"),
			&fogPart2{"{", bStr(""), "", eStr("}")}, false)
	case `\color{(...)}`:
		return findObserveGroups(, `\color{`, bStr(""), "", eStr("}"), nil, false)
	case `\color{(...)}{(...)}`:
		,  := findObserveGroups(, `\color{`, bStr(""), "", eStr("}"),
			&fogPart2{"{", bStr(""), "", eStr("}")}, false)
		if  != nil ||  != nil {
			return , 
		}
		return findObserveGroups(, `\color`, bStr(`\`), "", eRe(reBeforeBrace),
			&fogPart2{"{", bStr(""), "", eStr("}")}, false)
	case `\ce{(...)}`:
		return findObserveGroups(, `\ce{`, bStr(""), "", eStr("}"), nil, false)
	case `\pu{(...)}`:
		return findObserveGroups(, `\pu{`, bStr(""), "", eStr("}"), nil, false)
	}
	,  := .Regexes[]
	if ! {
		return nil, parserError("MhchemBugP", "mhchem bug P. Please report. ("++")")
	}
	, ,  := regexMatchToken(, )
	if ! {
		return nil, nil
	}
	return &PatternHit{Token: , Remainder: [:]}, nil
}