// 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 (
	
	
	
	
	
)

// jsRegexp is the small compatibility layer needed by mhchemparser's anchored
// JavaScript regular expressions.  Most expressions are mechanically lowered
// to Go's RE2 syntax.  The finite set that uses look-ahead is implemented by
// native anchored matchers below, avoiding a backtracking-regexp dependency.
type jsRegexp struct {
	re         *regexp.Regexp
	captureMap []int
	custom     func(string) (string, bool)
}

type jsGroup struct {
	text   string
	Length int
}

func ( jsGroup) () string { return .text }

type jsMatch struct {
	Index  int
	Length int
	text   string
	groups []jsGroup
}

// ECMAScript's \s set is fixed and differs from both RE2's ASCII \s and
// unicode.IsSpace (which also accepts characters such as U+0085).
const jsWhitespaceClassBody = `\x{0009}-\x{000D}\x{0020}\x{00A0}\x{1680}\x{2000}-\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}\x{FEFF}`

func isJSWhitespace( rune) bool {
	switch  {
	case '\u0009', '\u000A', '\u000B', '\u000C', '\u000D', '\u0020', '\u00A0', '\u1680', '\u2028', '\u2029', '\u202F', '\u205F', '\u3000', '\uFEFF':
		return true
	}
	return  >= '\u2000' &&  <= '\u200A'
}

func ( *jsMatch) () string    { return .text }
func ( *jsMatch) () []jsGroup { return .groups }

func ( *jsRegexp) ( string) (*jsMatch, error) {
	if .custom != nil {
		,  := .custom()
		if ! {
			return nil, nil
		}
		return &jsMatch{Length: len(), text: , groups: []jsGroup{{text: , Length: len()}}}, nil
	}
	 := .re.FindStringSubmatchIndex()
	if  == nil {
		return nil, nil
	}
	 := [[0]:[1]]
	 := []jsGroup{{text: , Length: len()}}
	for ,  := range .captureMap {
		 :=  * 2
		 := ""
		if +1 < len() && [] >= 0 {
			 = [[]:[+1]]
		}
		 = append(, jsGroup{text: , Length: len()})
	}
	return &jsMatch{Index: [0], Length: [1] - [0], text: , groups: }, nil
}

func compileJSRegexp( string) (*jsRegexp, error) {
	if  := customLookaheadMatcher();  != nil {
		return &jsRegexp{custom: }, nil
	}
	, ,  := lowerJSRegexp()
	if  != nil {
		return nil, 
	}
	,  := regexp.Compile()
	if  != nil {
		return nil, 
	}
	return &jsRegexp{re: , captureMap: }, nil
}

// lowerJSRegexp converts JavaScript Unicode/identity escapes and
// non-capturing groups.  captureMap retains only the groups that JavaScript
// would expose, despite the extra Go groups used for (?:...).
func lowerJSRegexp( string) (string, []int, error) {
	var  strings.Builder
	var  []int
	 := 0
	 := false
	for  := 0;  < len(); {
		if [] == '[' {
			 = true
			.WriteByte([])
			++
			continue
		}
		if [] == ']' &&  {
			 = false
			.WriteByte([])
			++
			continue
		}
		if [] == '\\' {
			if +1 >= len() {
				return "", nil, fmt.Errorf("dangling escape")
			}
			if +6 <= len() && [+1] == 'u' {
				if ,  := strconv.ParseUint([+2:+6], 16, 16);  == nil {
					.WriteString(`\x{`)
					.WriteString([+2 : +6])
					.WriteByte('}')
					 += 6
					continue
				}
			}
			 := [+1]
			switch  {
			case 's':
				if  {
					.WriteString(jsWhitespaceClassBody)
				} else {
					.WriteByte('[')
					.WriteString(jsWhitespaceClassBody)
					.WriteByte(']')
				}
			case '/', ',', '_', ' ', ';', ':':
				.WriteByte()
			case '-':
				.WriteString(`\x{2d}`)
			default:
				.WriteByte('\\')
				.WriteByte()
			}
			 += 2
			continue
		}
		if [] == '(' && ! {
			if strings.HasPrefix([:], "(?:") {
				++
				.WriteByte('(')
				 += 3
				continue
			}
			if strings.HasPrefix([:], "(?=") || strings.HasPrefix([:], "(?!") {
				return "", nil, fmt.Errorf("unsupported look-ahead in %q", )
			}
			++
			 = append(, )
		}
		.WriteByte([])
		++
	}
	return .String(), , nil
}

var (
	amountNumber = regexp.MustCompile(`^(\([+-]?[0-9]+/[0-9]+\)|[+-]?([0-9]+|\$[a-z]\$|[a-z])/[0-9]+|[+-]?[0-9]+[.,][0-9]+|[+-]?\.[0-9]+|[+-]?[0-9]+)`)
	parenLower   = regexp.MustCompile(`^\([a-z]+\)`)
)

var greekCommands = []string{
	"alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", "mu", "nu", "xi", "omicron", "pi", "rho", "sigma", "tau", "upsilon", "phi", "chi", "psi", "omega",
	"Gamma", "Delta", "Theta", "Lambda", "Xi", "Pi", "Sigma", "Upsilon", "Phi", "Psi", "Omega",
}

var lowerGreekCommands = greekCommands[:24]

func isASCIILetter( byte) bool { return  >= 'a' &&  <= 'z' ||  >= 'A' &&  <= 'Z' }

func commandMatch( string,  []string) (int, bool) {
	if !strings.HasPrefix(, `\`) {
		return 0, false
	}
	for ,  := range  {
		 := `\` + 
		if !strings.HasPrefix(, ) {
			continue
		}
		 := len()
		if  == len() {
			return , true
		}
		if strings.HasPrefix([:], "{}") {
			return  + 2, true
		}
		,  := utf8.DecodeRuneInString([:])
		if isJSWhitespace() {
			 += 
			for  < len() {
				,  = utf8.DecodeRuneInString([:])
				if !isJSWhitespace() {
					break
				}
				 += 
			}
			return , true
		}
		if !isASCIILetter([]) {
			return , true
		}
	}
	return 0, false
}

func lettersMatch( string) int {
	 := 0
	for  < len() {
		,  := utf8.DecodeRuneInString([:])
		if isASCIILetter([]) ||  >= 'α' &&  <= 'ω' ||  >= 'Α' &&  <= 'Ω' ||  == '?' ||  == '@' {
			 += 
			continue
		}
		if ,  := commandMatch([:], greekCommands);  {
			 += 
			continue
		}
		break
	}
	return 
}

func lowerGreekFullMatch( string) bool {
	 := 0
	if  < len() && [] == '$' {
		++
	}
	if  >= len() {
		return false
	}
	,  := utf8.DecodeRuneInString([:])
	if  >= 'α' &&  <= 'ω' {
		 += 
	} else {
		 := false
		for ,  := range lowerGreekCommands {
			 := `\` + 
			if strings.HasPrefix([:], ) {
				 += len()
				 = true
				for  < len() {
					,  = utf8.DecodeRuneInString([:])
					if !isJSWhitespace() {
						break
					}
					 += 
				}
				break
			}
		}
		if ! {
			return false
		}
	}
	if  < len() && [] == '$' {
		++
	}
	if  == len() {
		return true
	}
	if strings.HasPrefix([:], "{}") {
		 += 2
	} else {
		for  < len() {
			,  = utf8.DecodeRuneInString([:])
			if !isJSWhitespace() {
				return false
			}
			 += 
		}
	}
	return  == len()
}

func nextNonSpaceUpper( string) bool {
	for len() > 0 {
		,  := utf8.DecodeRuneInString()
		if !isJSWhitespace() {
			return  >= 'A' &&  <= 'Z'
		}
		 = [:]
	}
	return false
}

func matchAmountMain( string) (string, bool) {
	if  := amountNumber.FindStringIndex();  != nil && [0] == 0 {
		 := [1]
		if  < len() && [] >= 'a' && [] <= 'z' && nextNonSpaceUpper([+1:]) {
			++
		}
		return [:], true
	}
	 := 0
	if  < len() && ([] == '+' || [] == '-') {
		++
	}
	if  < len() && [] >= 'a' && [] <= 'z' && nextNonSpaceUpper([+1:]) {
		return [:+1], true
	}
	if strings.HasPrefix(, "+") {
		if len() == 1 {
			return "+", true
		}
		,  := utf8.DecodeRuneInString([1:])
		if !isJSWhitespace() {
			return "+", true
		}
	}
	return "", false
}

func boundary( string,  int) bool { return  == len() || !isASCIILetter([]) }

func customLookaheadMatcher( string) func(string) (string, bool) {
	switch {
	case  == `^.`:
		return func( string) (string, bool) {
			if  == "" {
				return "", false
			}
			,  := utf8.DecodeRuneInString()
			if  == '\n' ||  == '\r' ||  == '\u2028' ||  == '\u2029' {
				return "", false
			}
			return [:], true
		}
	case  == `^\([a-z]{1,3}(?=[\),])`:
		return func( string) (string, bool) {
			if len() < 3 || [0] != '(' {
				return "", false
			}
			 := 1
			for  < len() &&  <= 3 && [] >= 'a' && [] <= 'z' {
				++
			}
			if  == 1 ||  >= len() || [] != ',' && [] != ')' {
				return "", false
			}
			return [:], true
		}
	case  == `^(?=\{)`:
		return func( string) (string, bool) { return "", strings.HasPrefix(, "{") }
	case strings.Contains(, `[a-z](?=\s*[A-Z])`) && strings.Contains(, `\+(?!\s)`):
		return matchAmountMain
	case  == `^(?:[A-Z][a-z]{0,2}|i)(?=,)`:
		return func( string) (string, bool) {
			if strings.HasPrefix(, "i,") {
				return "i", true
			}
			if  == "" || [0] < 'A' || [0] > 'Z' {
				return "", false
			}
			 := 1
			for  < len() &&  < 3 && [] >= 'a' && [] <= 'z' {
				++
			}
			return [:],  < len() && [] == ','
		}
	case strings.HasPrefix(, `^-(?=(?:[spd]|sp)`):
		return func( string) (string, bool) {
			if !strings.HasPrefix(, "-") {
				return "", false
			}
			 := [1:]
			for ,  := range []string{"sp", "s", "p", "d"} {
				if strings.HasPrefix(, ) {
					 := [len():]
					if  == "" {
						return "-", true
					}
					,  := utf8.DecodeRuneInString()
					if isJSWhitespace() || strings.ContainsRune(",;)]}", ) {
						return "-", true
					}
				}
			}
			return "", false
		}
	case strings.HasPrefix(, `^-(?=[\s_},;`) && strings.Contains(, `\([a-z]+\))`):
		return func( string) (string, bool) {
			if !strings.HasPrefix(, "-") {
				return "", false
			}
			 := [1:]
			if  == "" || strings.ContainsRune("_},;]/", rune([0])) || parenLower.MatchString() {
				return "-", true
			}
			,  := utf8.DecodeRuneInString()
			return "-", isJSWhitespace()
		}
	case  == `^-(?=[0-9])`:
		return func( string) (string, bool) {
			return "-", len() > 1 && [0] == '-' && [1] >= '0' && [1] <= '9'
		}
	case  == `^\.\.\.(?=$|[^.])`:
		return func( string) (string, bool) {
			return "...", strings.HasPrefix(, "...") && (len() == 3 || [3] != '.')
		}
	case  == `^[CMT](?=\[)`:
		return func( string) (string, bool) {
			if len() < 2 || !strings.ContainsRune("CMT", rune([0])) || [1] != '[' {
				return "", false
			}
			return [:1], true
		}
	case strings.HasPrefix(, `^\\ca(?:\s+|`):
		return func( string) (string, bool) {
			if !strings.HasPrefix(, `\ca`) {
				return "", false
			}
			 := 3
			for  < len() {
				,  := utf8.DecodeRuneInString([:])
				if !isJSWhitespace() {
					break
				}
				 += 
			}
			return [:],  > 3 ||  == len() || !isASCIILetter([])
		}
	case strings.HasPrefix(, `^\\(?:alpha|beta`) && strings.Contains(, `(?![a-zA-Z])`):
		return func( string) (string, bool) {
			,  := commandMatch(, greekCommands)
			if ! {
				return "", false
			}
			return [:], true
		}
	case  == `^(?:\^(?=_)|\_(?=\^)|[\^_]$)`:
		return func( string) (string, bool) {
			if strings.HasPrefix(, "^_") ||  == "^" {
				return "^", true
			}
			if strings.HasPrefix(, "_^") ||  == "_" {
				return "_", true
			}
			return "", false
		}
	case strings.HasPrefix(, `^(?:v|\(v\)|\^|\(\^\))(?=`):
		return func( string) (string, bool) {
			for ,  := range []string{"(v)", "(^)", "v", "^"} {
				if strings.HasPrefix(, ) {
					 := [len():]
					if  == "" {
						return , true
					}
					,  := utf8.DecodeRuneInString()
					if isJSWhitespace() || strings.ContainsRune(",;)]}", ) {
						return , true
					}
				}
			}
			return "", false
		}
	case strings.HasPrefix(, `^(?:[a-zA-Z\u03B1-`):
		return func( string) (string, bool) {
			 := lettersMatch()
			return [:],  > 0
		}
	case strings.HasPrefix(, `^(?:\$?[\u03B1-`) && strings.HasSuffix(, `$`):
		return func( string) (string, bool) { return , lowerGreekFullMatch() }
	case strings.HasPrefix(, `^(?:\+|(?:[\-=<>]`):
		return func( string) (string, bool) {
			if strings.HasPrefix(, "+") {
				return "+", true
			}
			for ,  := range []string{`$\approx$`, `\approx`, "<<", ">>", "-", "=", "<", ">"} {
				if strings.HasPrefix(, ) {
					 := [len():]
					if  == "" {
						return , true
					}
					,  := utf8.DecodeRuneInString()
					if isJSWhitespace() || [0] >= '0' && [0] <= '9' || len() > 1 && [0] == '-' && [1] >= '0' && [1] <= '9' {
						return , true
					}
				}
			}
			return "", false
		}
	case strings.HasPrefix(, `^(?:[0-9]{1,2}[spdfgh]`):
		return func( string) (string, bool) {
			 := 0
			for  < len() &&  < 2 && [] >= '0' && [] <= '9' {
				++
			}
			if strings.HasPrefix([:], "sp") && boundary(, +2) {
				return [:+2], true
			}
			if  > 0 &&  < len() && strings.ContainsRune("spdfgh", rune([])) && boundary(, +1) {
				return [:+1], true
			}
			return "", false
		}
	case  == `^\s(?=[A-Z\\$])`:
		return func( string) (string, bool) {
			if  == "" {
				return "", false
			}
			,  := utf8.DecodeRuneInString()
			return [:], isJSWhitespace() &&  < len() && ([] >= 'A' && [] <= 'Z' || [] == '\\' || [] == '$')
		}
	case strings.HasPrefix(, `^(?:pH|pOH|pC|pK|iPr|iBu)(?=`):
		return func( string) (string, bool) {
			for ,  := range []string{"pOH", "pH", "pC", "pK", "iPr", "iBu"} {
				if strings.HasPrefix(, ) && boundary(, len()) {
					return , true
				}
			}
			return "", false
		}
	case  == `^\{\}(?=\^)`:
		return func( string) (string, bool) { return "{}", strings.HasPrefix(, "{}^") }
	}
	return nil
}