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

var (
	reDigitsOnly = regexp.MustCompile(`^[1-9][0-9]*$`)
	reCelsiusC   = regexp.MustCompile("°C|\\^oC|\\^\\{o\\}C")
	reCelsiusF   = regexp.MustCompile("°F|\\^oF|\\^\\{o\\}F")
	reHalf       = regexp.MustCompile(`^([0-9]+|\$[a-z]\$|[a-z])\/([0-9]+)(\$[a-z]\$|[a-z])?$`)
)

// applyAction dispatches one action spec against the buffer / match token.
// Mirrors upstream actions::apply.
func applyAction( *parserCtx,  string,  *Buffer,  *MatchToken,  *ActionSpec) ([]Value, error) {
	 := .Type
	switch {
	case  == "ce" &&  == "output":
		return ceOutput(, , .Option)
	case  == "ce" &&  == "o after d":
		return ceOAfterD(, , )
	case  == "ce" &&  == "d= kv":
		SetSlot(&.D, tokenString())
		 := "kv"
		.DType = &
		return nil, nil
	case  == "ce" &&  == "charge or bond":
		return ceChargeOrBond(, , )
	case  == "ce" &&  == "- after o/d":
		 := optBool(.Option)
		return ceAfterOD(, , , )
	case  == "ce" &&  == "a to o":
		.O = .A
		.A = nil
		return nil, nil
	case  == "ce" &&  == "sb=true":
		.Sb = true
		return nil, nil
	case  == "ce" &&  == "sb=false":
		.Sb = false
		return nil, nil
	case  == "ce" &&  == "beginsWithBond=true":
		.BeginsWithBond = true
		return nil, nil
	case  == "ce" &&  == "beginsWithBond=false":
		.BeginsWithBond = false
		return nil, nil
	case  == "ce" &&  == "parenthesisLevel++":
		.ParenthesisLevel++
		return nil, nil
	case  == "ce" &&  == "parenthesisLevel--":
		.ParenthesisLevel--
		return nil, nil
	case  == "ce" &&  == "state of aggregation":
		,  := .go_(tokenString(), "o")
		if  != nil {
			return nil, 
		}
		return []Value{map[string]any{"type_": "state of aggregation", "p1": }}, nil
	case  == "ce" &&  == "comma":
		return ceComma(, )
	case  == "ce" &&  == "oxidation-output":
		,  := .go_(tokenString(), "oxidation")
		if  != nil {
			return nil, 
		}
		 := []Value{"{"}
		 = append(, ...)
		 = append(, "}")
		return , nil
	case  == "ce" &&  == "frac-output":
		if !.IsArray || len(.A) < 2 {
			return nil, errMsg("frac-output")
		}
		,  := .go_(.A[0], "ce")
		if  != nil {
			return nil, 
		}
		,  := .go_(.A[1], "ce")
		if  != nil {
			return nil, 
		}
		return []Value{map[string]any{"type_": "frac-ce", "p1": , "p2": }}, nil
	case  == "ce" && ( == "overset-output" ||  == "underset-output" ||  == "underbrace-output"):
		if !.IsArray || len(.A) < 2 {
			return nil, errMsg("two-arg output")
		}
		 := "underbrace"
		switch  {
		case "overset-output":
			 = "overset"
		case "underset-output":
			 = "underset"
		}
		,  := .go_(.A[0], "ce")
		if  != nil {
			return nil, 
		}
		,  := .go_(.A[1], "ce")
		if  != nil {
			return nil, 
		}
		return []Value{map[string]any{"type_": , "p1": , "p2": }}, nil
	case  == "ce" &&  == "color-output":
		if !.IsArray || len(.A) < 2 {
			return nil, errMsg("color-output")
		}
		,  := .go_(.A[1], "ce")
		if  != nil {
			return nil, 
		}
		return []Value{map[string]any{"type_": "color", "color1": .A[0], "color2": }}, nil
	case  == "ce" &&  == "operator":
		 := optString(.Option)
		if  == "" {
			 = matchStr()
		}
		return []Value{map[string]any{"type_": "operator", "kind_": }}, nil

	case  == "text" &&  == "output":
		if .Text != nil {
			 := *.Text
			.ClearAll()
			return []Value{map[string]any{"type_": "text", "p1": }}, nil
		}
		return nil, nil

	case  == "pq" &&  == "state of aggregation":
		,  := .go_(tokenString(), "o")
		if  != nil {
			return nil, 
		}
		return []Value{map[string]any{"type_": "state of aggregation subscript", "p1": }}, nil
	case ( == "pq" ||  == "bd") &&  == "color-output":
		if !.IsArray {
			return nil, errMsg("color bd/pq")
		}
		 := "bd"
		if  == "pq" {
			 = "pq"
		}
		,  := .go_(.A[1], )
		if  != nil {
			return nil, 
		}
		return []Value{map[string]any{"type_": "color", "color1": .A[0], "color2": }}, nil

	case  == "oxidation" &&  == "roman-numeral":
		return []Value{map[string]any{"type_": "roman numeral", "p1": matchStr()}}, nil

	case ( == "tex-math" ||  == "tex-math tight") &&  == "output":
		if .O != nil {
			 := *.O
			.ClearAll()
			return []Value{map[string]any{"type_": "tex-math", "p1": }}, nil
		}
		return nil, nil
	case  == "tex-math tight" &&  == "tight operator":
		 := matchStr()
		 := ""
		if .O != nil {
			 = *.O
		}
		 :=  + "{" +  + "}"
		.O = &
		return nil, nil

	case  == "9,9" &&  == "comma":
		return []Value{map[string]any{"type_": "commaDecimal"}}, nil

	case  == "pu" &&  == "enumber":
		return puEnumber(, )
	case  == "pu" &&  == "number^":
		return puNumberPow(, )
	case  == "pu" &&  == "space":
		return []Value{map[string]any{"type_": "pu-space-1"}}, nil
	case  == "pu" &&  == "output":
		return puOutput(, )

	case  == "pu-2" &&  == "cdot":
		return []Value{map[string]any{"type_": "tight cdot"}}, nil
	case  == "pu-2" &&  == "^(-1)":
		 := matchStr()
		 := ""
		if .Rm != nil {
			 = *.Rm
		}
		 :=  + "^{" +  + "}"
		.Rm = &
		return nil, nil
	case  == "pu-2" &&  == "space":
		return []Value{map[string]any{"type_": "pu-space-2"}}, nil
	case  == "pu-2" &&  == "output":
		return pu2Output(, )

	case  == "pu-9,9" &&  == "comma":
		return []Value{map[string]any{"type_": "commaDecimal"}}, nil
	case  == "pu-9,9" &&  == "output-0":
		return pu99Out(, true)
	case  == "pu-9,9" &&  == "output-o":
		return pu99Out(, false)
	}
	return globalAction(, , , , )
}

func optString( json.RawMessage) string {
	if len() == 0 {
		return ""
	}
	var  string
	if  := json.Unmarshal(, &);  != nil {
		return ""
	}
	return 
}

func optBool( json.RawMessage) bool {
	if len() == 0 {
		return false
	}
	var  bool
	if  := json.Unmarshal(, &);  != nil {
		return false
	}
	return 
}

func optInt( json.RawMessage) (int, bool) {
	if len() == 0 {
		return 0, false
	}
	var  int
	if  := json.Unmarshal(, &);  == nil {
		return , true
	}
	return 0, false
}

func matchStr( *MatchToken) string {
	if  == nil {
		return ""
	}
	if !.IsArray {
		return .S
	}
	if len(.A) > 0 {
		return .A[0]
	}
	return ""
}

func tokenString( *MatchToken) string {
	if  == nil {
		return ""
	}
	if !.IsArray {
		return .S
	}
	return strings.Join(.A, "")
}

func slotStr( *string) string {
	if  == nil {
		return ""
	}
	return *
}

func ceComma( *Buffer,  *MatchToken) ([]Value, error) {
	 := tokenString()
	 := strings.TrimRightFunc(, isJSWhitespace)
	 :=  != 
	 := "comma enumeration M"
	if  && .ParenthesisLevel == 0 {
		 = "comma enumeration L"
	}
	return []Value{map[string]any{"type_": , "p1": }}, nil
}

func ceOAfterD( *parserCtx,  *Buffer,  *MatchToken) ([]Value, error) {
	var  []Value
	 := .D != nil && reDigitsOnly.MatchString(*.D)
	if  {
		 := *.D
		.D = nil
		,  := ceOutput(, , nil)
		if  != nil {
			return nil, 
		}
		 = append(, ...)
		 = append(, map[string]any{"type_": "tinySkip"})
		SetSlot(&.B, )
	} else {
		,  := ceOutput(, , nil)
		if  != nil {
			return nil, 
		}
		 = append(, ...)
	}
	cat(&.O, )
	return , nil
}

func ceChargeOrBond( *parserCtx,  *Buffer,  *MatchToken) ([]Value, error) {
	if .BeginsWithBond {
		,  := ceOutput(, , nil)
		if  != nil {
			return nil, 
		}
		 = append(, map[string]any{"type_": "bond", "kind_": "-"})
		return , nil
	}
	SetSlot(&.D, tokenString())
	return nil, nil
}

func ceAfterOD( *parserCtx,  *Buffer,  *MatchToken,  bool) ([]Value, error) {
	 := tokenString()
	 := slotStr(.O)
	,  := matchPattern(.data, "orbital", )
	,  := matchPattern(.data, "one lowercase greek letter $", )
	,  := matchPattern(.data, "one lowercase latin letter $", )
	,  := matchPattern(.data, "$one lowercase latin letter$ $", )
	 :=  != nil && .Remainder == ""
	 :=  == "-" && ( ||  != nil ||  != nil ||  != nil)

	if  &&
		IsSlotEmpty(.A) && IsSlotEmpty(.B) && IsSlotEmpty(.P) &&
		IsSlotEmpty(.D) && IsSlotEmpty(.Q) &&
		 == nil &&  != nil {
		 := slotStr(.O)
		 := "$" +  + "$"
		.O = &
	}

	var  []Value
	if  {
		,  := ceOutput(, , nil)
		if  != nil {
			return nil, 
		}
		 = append(, ...)
		 = append(, map[string]any{"type_": "hyphen"})
		return , nil
	}

	,  := matchPattern(.data, "digits", slotStr(.D))
	 :=  != nil && .Remainder == ""
	if  &&  {
		cat(&.D, )
		,  := ceOutput(, , nil)
		if  != nil {
			return nil, 
		}
		 = append(, ...)
	} else {
		,  := ceOutput(, , nil)
		if  != nil {
			return nil, 
		}
		 = append(, ...)
		 = append(, map[string]any{"type_": "bond", "kind_": "-"})
	}
	return , nil
}

func ceOutput( *parserCtx,  *Buffer,  json.RawMessage) ([]Value, error) {
	,  := optInt()

	if IsSlotEmpty(.R) {
		 := IsSlotEmpty(.A) && IsSlotEmpty(.B) && IsSlotEmpty(.P) &&
			IsSlotEmpty(.O) && IsSlotEmpty(.Q) && IsSlotEmpty(.D) && !
		if  {
			.ClearSoft()
			return nil, nil
		}

		var  []Value
		if .Sb {
			 = append(, map[string]any{"type_": "entitySkip"})
		}

		var  *string
		if .DType != nil {
			 := *.DType
			 = &
		}

		if IsSlotEmpty(.O) && IsSlotEmpty(.Q) && IsSlotEmpty(.D) &&
			IsSlotEmpty(.B) && IsSlotEmpty(.P) &&
			!( &&  == 2) {
			.O = .A
			.A = nil
		} else if IsSlotEmpty(.O) && IsSlotEmpty(.Q) && IsSlotEmpty(.D) &&
			(!IsSlotEmpty(.B) || !IsSlotEmpty(.P)) {
			.O = .A
			.A = nil
			.D = .B
			.B = nil
			.Q = .P
			.P = nil
		} else if !IsSlotEmpty(.O) &&  != nil && * == "kv" {
			,  := matchPattern(.data, "d-oxidation$", slotStr(.D))
			if  != nil {
				 := "oxidation"
				 = &
			} else if IsSlotEmpty(.Q) {
				 = nil
			}
		}

		,  := .go_(slotStr(.A), "a")
		if  != nil {
			return nil, 
		}
		,  := .go_(slotStr(.B), "bd")
		if  != nil {
			return nil, 
		}
		,  := .go_(slotStr(.P), "pq")
		if  != nil {
			return nil, 
		}
		,  := .go_(slotStr(.O), "o")
		if  != nil {
			return nil, 
		}
		,  := .go_(slotStr(.Q), "pq")
		if  != nil {
			return nil, 
		}
		 := "bd"
		if  != nil && * == "oxidation" {
			 = "oxidation"
		}
		,  := .go_(slotStr(.D), )
		if  != nil {
			return nil, 
		}
		.A, .B, .P, .O, .Q, .D = nil, nil, nil, nil, nil, nil

		 := map[string]any{
			"type_": "chemfive",
			"a":     ,
			"b":     ,
			"p":     ,
			"o":     ,
			"q":     ,
			"d":     ,
		}
		if  != nil {
			["dType"] = *
		}
		 = append(, )
		.ClearSoft()
		return , nil
	}

	 := slotStr(.R)
	 := slotStr(.Rdt)
	 := slotStr(.Rd)
	var  []Value
	switch  {
	case "M":
		var  error
		,  = .go_(, "tex-math")
		if  != nil {
			return nil, 
		}
	case "T":
		 = []Value{map[string]any{"type_": "text", "p1": }}
	default:
		var  error
		,  = .go_(, "ce")
		if  != nil {
			return nil, 
		}
	}

	 := slotStr(.Rqt)
	 := slotStr(.Rq)
	var  []Value
	switch  {
	case "M":
		var  error
		,  = .go_(, "tex-math")
		if  != nil {
			return nil, 
		}
	case "T":
		 = []Value{map[string]any{"type_": "text", "p1": }}
	default:
		var  error
		,  = .go_(, "ce")
		if  != nil {
			return nil, 
		}
	}

	 := map[string]any{
		"type_": "arrow",
		"r":     ,
		"rd":    ,
		"rq":    ,
	}
	.ClearSoft()
	return []Value{}, nil
}

func puEnumber( *parserCtx,  *MatchToken) ([]Value, error) {
	if !.IsArray {
		return nil, nil
	}
	 := .A
	 := func( int) string {
		if  < len() {
			return []
		}
		return ""
	}
	var  []Value
	 := (0)
	if  == "+-" ||  == "+/-" {
		 = append(, "\\pm ")
	} else if  != "" {
		 = append(, )
	}
	if (1) != "" {
		,  := .go_([1], "pu-9,9")
		if  != nil {
			return nil, 
		}
		 = append(, ...)
		if  := (2);  != "" {
			if strings.ContainsAny(, ",.") {
				,  := .go_(, "pu-9,9")
				if  != nil {
					return nil, 
				}
				 = append(, ...)
			} else {
				 = append(, )
			}
		}
		 := strings.TrimSpace((4))
		 := strings.TrimSpace((3))
		 := 
		if  == "" {
			 = 
		}
		if  != "" {
			if  == "e" || strings.HasPrefix(, "*") {
				 = append(, map[string]any{"type_": "cdot"})
			} else {
				 = append(, map[string]any{"type_": "times"})
			}
		}
	}
	if  := (5);  != "" {
		 = append(, "10^{"++"}")
	}
	return , nil
}

func puNumberPow( *parserCtx,  *MatchToken) ([]Value, error) {
	if !.IsArray {
		return nil, nil
	}
	 := .A
	var  []Value
	 := func( int) string {
		if  < len() {
			return []
		}
		return ""
	}
	 := (0)
	if  == "+-" ||  == "+/-" {
		 = append(, "\\pm ")
	} else if  != "" {
		 = append(, )
	}
	if  := (1);  != "" || len() > 1 {
		,  := .go_(, "pu-9,9")
		if  != nil {
			return nil, 
		}
		 = append(, ...)
	}
	if  := (2);  != "" || len() > 2 {
		 = append(, "^{"++"}")
	}
	return , nil
}

func puOutput( *parserCtx,  *Buffer) ([]Value, error) {
	 := slotStr(.D)
	if ,  := matchPattern(.data, "{(...)}", );  != nil && .Remainder == "" && !.Token.IsArray {
		 = .Token.S
	}
	 := slotStr(.Q)
	if ,  := matchPattern(.data, "{(...)}", );  != nil && .Remainder == "" && !.Token.IsArray {
		 = .Token.S
	}
	 = reCelsiusC.ReplaceAllString(, "{}^{\\circ}C")
	 = reCelsiusC.ReplaceAllString(, "{}^{\\circ}C")
	 = reCelsiusF.ReplaceAllString(, "{}^{\\circ}F")
	 = reCelsiusF.ReplaceAllString(, "{}^{\\circ}F")

	var  []Value
	if  != "" {
		,  := .go_(, "pu")
		if  != nil {
			return nil, 
		}
		,  := .go_(, "pu")
		if  != nil {
			return nil, 
		}
		if .O != nil && *.O == "//" {
			 = []Value{map[string]any{"type_": "pu-frac", "p1": , "p2": }}
		} else {
			 = 
			if len() > 1 || len() > 1 {
				 = append(, map[string]any{"type_": " / "})
			} else {
				 = append(, map[string]any{"type_": "/"})
			}
			 = append(, ...)
		}
	} else {
		,  := .go_(, "pu-2")
		if  != nil {
			return nil, 
		}
		 = 
	}
	.ClearAll()
	return , nil
}

func pu2Output( *parserCtx,  *Buffer) ([]Value, error) {
	var  []Value
	if .Rm != nil {
		 := *.Rm
		.Rm = nil
		,  := matchPattern(.data, "{(...)}", )
		if  != nil && .Remainder == "" {
			if !.Token.IsArray {
				,  := .go_(.Token.S, "pu")
				if  != nil {
					return nil, 
				}
				 = 
			}
		} else {
			 = []Value{map[string]any{"type_": "rm", "p1": }}
		}
	}
	.ClearAll()
	return , nil
}

func pu99Out( *Buffer,  bool) ([]Value, error) {
	 := slotStr(.Text)
	.Text = nil
	var  []Value
	if len() > 4 {
		if  {
			 := len() % 3
			if  == 0 {
				 = 3
			}
			 := len() - 3
			for  > 0 {
				 = append(, [:+3])
				 = append(, map[string]any{"type_": "1000 separator"})
				 -= 3
			}
			 = append(, [:])
			// reverse
			for ,  := 0, len()-1;  < ; ,  = +1, -1 {
				[], [] = [], []
			}
		} else {
			 := len() - 3
			 := 0
			for  <  {
				 = append(, [:+3])
				 = append(, map[string]any{"type_": "1000 separator"})
				 += 3
			}
			 = append(, [:])
		}
	} else {
		 = []Value{}
	}
	.ClearAll()
	return , nil
}

func globalAction( *parserCtx,  string,  *Buffer,  *MatchToken,  *ActionSpec) ([]Value, error) {
	switch .Type {
	case "a=":
		cat(&.A, )
		return nil, nil
	case "b=":
		cat(&.B, )
		return nil, nil
	case "p=":
		cat(&.P, )
		return nil, nil
	case "o=":
		cat(&.O, )
		return nil, nil
	case "q=":
		cat(&.Q, )
		return nil, nil
	case "d=":
		cat(&.D, )
		return nil, nil
	case "rm=":
		cat(&.Rm, )
		return nil, nil
	case "text=":
		cat(&.Text, )
		return nil, nil
	case "r=":
		SetSlot(&.R, tokenString())
		return nil, nil
	case "rdt=":
		SetSlot(&.Rdt, tokenString())
		return nil, nil
	case "rqt=":
		SetSlot(&.Rqt, tokenString())
		return nil, nil
	case "rd=":
		SetSlot(&.Rd, tokenString())
		return nil, nil
	case "rq=":
		SetSlot(&.Rq, tokenString())
		return nil, nil
	case "insert":
		 := optString(.Option)
		return []Value{map[string]any{"type_": }}, nil
	case "insert+p1":
		return []Value{map[string]any{"type_": optString(.Option), "p1": matchStr()}}, nil
	case "insert+p1+p2":
		if !.IsArray || len(.A) < 2 {
			return nil, errMsg("insert+p1+p2")
		}
		return []Value{map[string]any{
			"type_": optString(.Option),
			"p1":    .A[0],
			"p2":    .A[1],
		}}, nil
	case "copy":
		return []Value{tokenString()}, nil
	case "write":
		return []Value{optString(.Option)}, nil
	case "rm":
		return []Value{map[string]any{"type_": "rm", "p1": matchStr()}}, nil
	case "text":
		return .go_(tokenString(), "text")
	case "{text}":
		 := []Value{"{"}
		,  := .go_(tokenString(), "text")
		if  != nil {
			return nil, 
		}
		 = append(, ...)
		 = append(, "}")
		return , nil
	case "tex-math":
		return .go_(tokenString(), "tex-math")
	case "tex-math tight":
		return .go_(tokenString(), "tex-math tight")
	case "bond":
		 := optString(.Option)
		if  == "" {
			 = matchStr()
		}
		return []Value{map[string]any{"type_": "bond", "kind_": }}, nil
	case "color0-output":
		return []Value{map[string]any{"type_": "color0", "color": matchStr()}}, nil
	case "ce":
		return .go_(tokenString(), "ce")
	case "pu":
		return .go_(tokenString(), "pu")
	case "1/2":
		return halfAction()
	case "9,9":
		return .go_(tokenString(), "9,9")
	case "operator":
		 := optString(.Option)
		if  == "" {
			 = matchStr()
		}
		return []Value{map[string]any{"type_": "operator", "kind_": }}, nil
	}
	return nil, parserError("MhchemBugA", "mhchem bug A. Please report. ("+.Type+")")
}

func cat( **string,  *MatchToken) {
	 := tokenString()
	if * == nil {
		 := 
		* = &
		return
	}
	 := ** + 
	* = &
}

func halfAction( *MatchToken) ([]Value, error) {
	 := tokenString()
	var  []Value
	if strings.HasPrefix(, "+") || strings.HasPrefix(, "-") {
		 = append(, [:1])
		 = [1:]
	}
	 := reHalf.FindStringSubmatch()
	if  == nil {
		return nil, nil
	}
	 := strings.ReplaceAll([1], "$", "")
	 := [2]
	 = append(, map[string]any{"type_": "frac", "p1": , "p2": })
	if len() > 3 && [3] != "" {
		 := strings.ReplaceAll([3], "$", "")
		 = append(, map[string]any{"type_": "tex-math", "p1": })
	}
	return , nil
}