// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
// 📄 Github Repository: https://github.com/gofiber/fiber
// 📌 API Documentation: https://docs.gofiber.io
// ⚠️ This path parser was inspired by ucarion/urlpath (MIT License).
// 💖 Maintained and modified for Fiber by @renewerner87

package fiber

import (
	
	
	
	
	

	

	
)

// routeParser holds the path segments and param names
type routeParser struct {
	segs          []*routeSegment // the parsed segments of the route
	params        []string        // that parameter names the parsed route
	wildCardCount int             // number of wildcard parameters, used internally to give the wildcard parameter its number
	plusCount     int             // number of plus parameters, used internally to give the plus parameter its number
}

// paramsSeg holds the segment metadata
type routeSegment struct {
	// const information
	Const string // constant part of the route
	// parameter information
	IsParam     bool   // Truth value that indicates whether it is a parameter or a constant part
	ParamName   string // name of the parameter for access to it, for wildcards and plus parameters access iterators starting with 1 are added
	ComparePart string // search part to find the end of the parameter
	PartCount   int    // how often is the search part contained in the non-param segments? -> necessary for greedy search
	IsGreedy    bool   // indicates whether the parameter is greedy or not, is used with wildcard and plus
	IsOptional  bool   // indicates whether the parameter is optional or not
	// common information
	IsLast           bool          // shows if the segment is the last one for the route
	HasOptionalSlash bool          // segment has the possibility of an optional slash
	Constraints      []*Constraint // Constraint type if segment is a parameter, if not it will be set to noConstraint by default
	Length           int           // length of the parameter for segment, when its 0 then the length is undetermined
	// future TODO: add support for optional groups "/abc(/def)?"
}

// different special routing signs
const (
	wildcardParam                byte = '*'  // indicates an optional greedy parameter
	plusParam                    byte = '+'  // indicates a required greedy parameter
	optionalParam                byte = '?'  // concludes a parameter by name and makes it optional
	paramStarterChar             byte = ':'  // start character for a parameter with name
	slashDelimiter               byte = '/'  // separator for the route, unlike the other delimiters this character at the end can be optional
	escapeChar                   byte = '\\' // escape character
	paramConstraintStart         byte = '<'  // start of type constraint for a parameter
	paramConstraintEnd           byte = '>'  // end of type constraint for a parameter
	paramConstraintSeparator     byte = ';'  // separator of type constraints for a parameter
	paramConstraintDataStart     byte = '('  // start of data of type constraint for a parameter
	paramConstraintDataEnd       byte = ')'  // end of data of type constraint for a parameter
	paramConstraintDataSeparator byte = ','  // separator of datas of type constraint for a parameter
)

// TypeConstraint parameter constraint types
type TypeConstraint int16

type Constraint struct {
	ID            TypeConstraint
	RegexCompiler *regexp.Regexp
	Data          []string
}

const (
	noConstraint TypeConstraint = iota + 1
	intConstraint
	boolConstraint
	floatConstraint
	alphaConstraint
	datetimeConstraint
	guidConstraint
	minLenConstraint
	maxLenConstraint
	lenConstraint
	betweenLenConstraint
	minConstraint
	maxConstraint
	rangeConstraint
	regexConstraint
)

// list of possible parameter and segment delimiter
var (
	// slash has a special role, unlike the other parameters it must not be interpreted as a parameter
	routeDelimiter = []byte{slashDelimiter, '-', '.'}
	// list of greedy parameters
	greedyParameters = []byte{wildcardParam, plusParam}
	// list of chars for the parameter recognizing
	parameterStartChars = []byte{wildcardParam, plusParam, paramStarterChar}
	// list of chars of delimiters and the starting parameter name char
	parameterDelimiterChars = append([]byte{paramStarterChar, escapeChar}, routeDelimiter...)
	// list of chars to find the end of a parameter
	parameterEndChars = append([]byte{optionalParam}, parameterDelimiterChars...)
	// list of parameter constraint start
	parameterConstraintStartChars = []byte{paramConstraintStart}
	// list of parameter constraint end
	parameterConstraintEndChars = []byte{paramConstraintEnd}
	// list of parameter separator
	parameterConstraintSeparatorChars = []byte{paramConstraintSeparator}
	// list of parameter constraint data start
	parameterConstraintDataStartChars = []byte{paramConstraintDataStart}
	// list of parameter constraint data end
	parameterConstraintDataEndChars = []byte{paramConstraintDataEnd}
	// list of parameter constraint data separator
	parameterConstraintDataSeparatorChars = []byte{paramConstraintDataSeparator}
)

// RoutePatternMatch checks if a given path matches a Fiber route pattern.
func (,  string,  ...Config) bool {
	// See logic in (*Route).match and (*App).register
	var  [maxParams]string

	 := Config{}
	if len() > 0 {
		 = [0]
	}

	if  == "" {
		 = "/"
	}

	// Cannot have an empty pattern
	if  == "" {
		 = "/"
	}
	// Pattern always start with a '/'
	if [0] != '/' {
		 = "/" + 
	}

	 := 

	// Case-sensitive routing, all to lowercase
	if !.CaseSensitive {
		 = utils.ToLower()
		 = utils.ToLower()
	}
	// Strict routing, remove trailing slashes
	if !.StrictRouting && len() > 1 {
		 = utils.TrimRight(, '/')
	}

	 := parseRoute()

	if  == "/" &&  == "/" {
		return true
		// '*' wildcard matches any path
	} else if  == "/*" {
		return true
	}

	// Does this route have parameters
	if len(.params) > 0 {
		if  := .getMatch(, , &, false);  {
			return true
		}
	}
	// Check for a simple match
	 = RemoveEscapeChar()
	if len() == len() &&  ==  {
		return true
	}
	// No match
	return false
}

// parseRoute analyzes the route and divides it into segments for constant areas and parameters,
// this information is needed later when assigning the requests to the declared routes
func parseRoute( string) routeParser {
	 := routeParser{}

	 := ""
	for len() > 0 {
		 := findNextParamPosition()
		// handle the parameter part
		if  == 0 {
			,  := .analyseParameterPart()
			.params, .segs,  = append(.params, .ParamName), append(.segs, ), 
		} else {
			,  := .analyseConstantPart(, )
			.segs,  = append(.segs, ), 
		}

		// reduce the pattern by the processed parts
		if len() == len() {
			break
		}
		 = [len():]
	}
	// mark last segment
	if len(.segs) > 0 {
		.segs[len(.segs)-1].IsLast = true
	}
	.segs = addParameterMetaInfo(.segs)

	return 
}

// addParameterMetaInfo add important meta information to the parameter segments
// to simplify the search for the end of the parameter
func addParameterMetaInfo( []*routeSegment) []*routeSegment {
	var  string
	 := len()
	// loop from end to begin
	for  :=  - 1;  >= 0; -- {
		// set the compare part for the parameter
		if [].IsParam {
			// important for finding the end of the parameter
			[].ComparePart = RemoveEscapeChar()
		} else {
			 = [].Const
			if len() > 1 {
				 = utils.TrimRight(, slashDelimiter)
			}
		}
	}

	// loop from begin to end
	for  := 0;  < ; ++ {
		// check how often the compare part is in the following const parts
		if [].IsParam {
			// check if parameter segments are directly after each other and if one of them is greedy
			// in case the next parameter or the current parameter is not a wildcard it's not greedy, we only want one character
			if  > +1 && ![].IsGreedy && [+1].IsParam && ![+1].IsGreedy {
				[].Length = 1
			}
			if [].ComparePart == "" {
				continue
			}
			for  :=  + 1;  <= len()-1; ++ {
				if ![].IsParam {
					// count is important for the greedy match
					[].PartCount += strings.Count([].Const, [].ComparePart)
				}
			}
			// check if the end of the segment is a optional slash and then if the segement is optional or the last one
		} else if [].Const[len([].Const)-1] == slashDelimiter && ([].IsLast || ( > +1 && [+1].IsOptional)) {
			[].HasOptionalSlash = true
		}
	}

	return 
}

// findNextParamPosition search for the next possible parameter start position
func findNextParamPosition( string) int {
	 := findNextNonEscapedCharsetPosition(, parameterStartChars)
	if  != -1 && len() >  && [] != wildcardParam {
		// search for parameter characters for the found parameter start,
		// if there are more, move the parameter start to the last parameter char
		for  := findNextNonEscapedCharsetPosition([+1:], parameterStartChars);  == 0; {
			++
			if len() >  {
				break
			}
		}
	}

	return 
}

// analyseConstantPart find the end of the constant part and create the route segment
func (*routeParser) ( string,  int) (string, *routeSegment) {
	// handle the constant part
	 := 
	if  != -1 {
		// remove the constant part until the parameter
		 = [:]
	}
	 := RemoveEscapeChar()
	return , &routeSegment{
		Const:  ,
		Length: len(),
	}
}

// analyseParameterPart find the parameter end and create the route segment
func ( *routeParser) ( string) (string, *routeSegment) {
	 := [0] == wildcardParam
	 := [0] == plusParam

	var  int
	if strings.ContainsRune(, rune(paramConstraintStart)) && strings.ContainsRune(, rune(paramConstraintEnd)) {
		 = findNextCharsetPositionConstraint([1:], parameterEndChars)
	} else {
		 = findNextNonEscapedCharsetPosition([1:], parameterEndChars)
	}

	 := -1
	 := -1
	// handle wildcard end
	switch {
	case , :
		 = 0
	case  == -1:
		 = len() - 1
	case !isInCharset([+1], parameterDelimiterChars):
		++
	}

	// find constraint part if exists in the parameter part and remove it
	if  > 0 {
		 = findNextNonEscapedCharsetPosition([0:], parameterConstraintStartChars)
		 = findLastCharsetPosition([0:+1], parameterConstraintEndChars)
	}

	// cut params part
	 := [0 : +1]
	 := RemoveEscapeChar(GetTrimmedParam())

	// Check has constraint
	var  []*Constraint

	if  :=  != -1 &&  != -1;  {
		 := [+1 : ]
		 := splitNonEscaped(, string(parameterConstraintSeparatorChars))
		 = make([]*Constraint, 0, len())

		for ,  := range  {
			 := findNextNonEscapedCharsetPosition(, parameterConstraintDataStartChars)
			 := findLastCharsetPosition(, parameterConstraintDataEndChars)

			// Assign constraint
			if  != -1 &&  != -1 {
				 := &Constraint{
					ID: getParamConstraintType([:]),
				}

				// remove escapes from data
				if .ID != regexConstraint {
					.Data = splitNonEscaped([+1:], string(parameterConstraintDataSeparatorChars))
					if len(.Data) == 1 {
						.Data[0] = RemoveEscapeChar(.Data[0])
					} else if len(.Data) == 2 { // This is fine, we simply expect two parts
						.Data[0] = RemoveEscapeChar(.Data[0])
						.Data[1] = RemoveEscapeChar(.Data[1])
					}
				}

				// Precompile regex if has regex constraint
				if .ID == regexConstraint {
					.Data = []string{[+1 : ]}
					.RegexCompiler = regexp.MustCompile(.Data[0])
				}

				 = append(, )
			} else {
				 = append(, &Constraint{
					ID:   getParamConstraintType(),
					Data: []string{},
				})
			}
		}

		 = RemoveEscapeChar(GetTrimmedParam([0:]))
	}

	// add access iterator to wildcard and plus
	if  {
		.wildCardCount++
		 += strconv.Itoa(.wildCardCount)
	} else if  {
		.plusCount++
		 += strconv.Itoa(.plusCount)
	}

	 := &routeSegment{
		ParamName:  ,
		IsParam:    true,
		IsOptional:  || [] == optionalParam,
		IsGreedy:    || ,
	}

	if len() > 0 {
		.Constraints = 
	}

	return , 
}

// isInCharset check is the given character in the charset list
func isInCharset( byte,  []byte) bool {
	for ,  := range  {
		if  ==  {
			return true
		}
	}
	return false
}

// findNextCharsetPosition search the next char position from the charset
func findNextCharsetPosition( string,  []byte) int {
	 := -1
	for ,  := range  {
		if  := strings.IndexByte(, );  != -1 && ( <  ||  == -1) {
			 = 
		}
	}

	return 
}

// findNextCharsetPosition search the last char position from the charset
func findLastCharsetPosition( string,  []byte) int {
	 := -1
	for ,  := range  {
		if  := strings.LastIndexByte(, );  != -1 && ( <  ||  == -1) {
			 = 
		}
	}

	return 
}

// findNextCharsetPositionConstraint search the next char position from the charset
// unlike findNextCharsetPosition, it takes care of constraint start-end chars to parse route pattern
func findNextCharsetPositionConstraint( string,  []byte) int {
	 := findNextNonEscapedCharsetPosition(, parameterConstraintStartChars)
	 := findNextNonEscapedCharsetPosition(, parameterConstraintEndChars)
	 := -1

	for ,  := range  {
		 := strings.IndexByte(, )

		if  != -1 && ( <  ||  == -1) {
			if ( >  &&  > ) || ( <  &&  < ) {
				 = 
			}
		}
	}

	return 
}

// findNextNonEscapedCharsetPosition search the next char position from the charset and skip the escaped characters
func findNextNonEscapedCharsetPosition( string,  []byte) int {
	 := findNextCharsetPosition(, )
	for  > 0 && [-1] == escapeChar {
		if len() == +1 {
			// escaped character is at the end
			return -1
		}
		 := findNextCharsetPosition([+1:], )
		if  == -1 {
			return -1
		}
		// the previous character is taken into consideration
		 =  +  + 1
	}

	return 
}

// splitNonEscaped slices s into all substrings separated by sep and returns a slice of the substrings between those separators
// This function also takes a care of escape char when splitting.
func splitNonEscaped(,  string) []string {
	var  []string
	 := findNextNonEscapedCharsetPosition(, []byte())

	for  > -1 {
		 = append(, [:])
		 = [+len():]
		 = findNextNonEscapedCharsetPosition(, []byte())
	}

	return append(, )
}

// getMatch parses the passed url and tries to match it against the route segments and determine the parameter positions
func ( *routeParser) (,  string,  *[maxParams]string,  bool) bool { //nolint: revive // Accepting a bool param is fine here
	var , ,  int
	for ,  := range .segs {
		 = len()
		// check const segment
		if !.IsParam {
			 = .Length
			// is optional part or the const part must match with the given string
			// check if the end of the segment is an optional slash
			if .HasOptionalSlash &&  == -1 &&  == .Const[:-1] {
				--
			} else if !( <=  && [:] == .Const) {
				return false
			}
		} else {
			// determine parameter length
			 = findParamLen(, )
			if !.IsOptional &&  == 0 {
				return false
			}
			// take over the params positions
			[] = [:]

			if !(.IsOptional &&  == 0) {
				// check constraint
				for ,  := range .Constraints {
					if  := .CheckConstraint([]); ! {
						return false
					}
				}
			}

			++
		}

		// reduce founded part from the string
		if  > 0 {
			,  = [:], [:]
		}
	}
	if  != "" && ! {
		return false
	}

	return true
}

// findParamLen for the expressjs wildcard behavior (right to left greedy)
// look at the other segments and take what is left for the wildcard from right to left
func findParamLen( string,  *routeSegment) int {
	if .IsLast {
		return findParamLenForLastSegment(, )
	}

	if .Length != 0 && len() >= .Length {
		return .Length
	} else if .IsGreedy {
		// Search the parameters until the next constant part
		// special logic for greedy params
		 := strings.Count(, .ComparePart)
		if  > 1 {
			return findGreedyParamLen(, , )
		}
	}

	if len(.ComparePart) == 1 {
		if  := strings.IndexByte(, .ComparePart[0]);  != -1 {
			return 
		}
	} else if  := strings.Index(, .ComparePart);  != -1 {
		// if the compare part was found, but contains a slash although this part is not greedy, then it must not match
		// example: /api/:param/fixedEnd -> path: /api/123/456/fixedEnd = no match , /api/123/fixedEnd = match
		if !.IsGreedy && strings.IndexByte([:], slashDelimiter) != -1 {
			return 0
		}
		return 
	}

	return len()
}

// findParamLenForLastSegment get the length of the parameter if it is the last segment
func findParamLenForLastSegment( string,  *routeSegment) int {
	if !.IsGreedy {
		if  := strings.IndexByte(, slashDelimiter);  != -1 {
			return 
		}
	}

	return len()
}

// findGreedyParamLen get the length of the parameter for greedy segments from right to left
func findGreedyParamLen( string,  int,  *routeSegment) int {
	// check all from right to left segments
	for  := .PartCount;  > 0 &&  > 0; -- {
		--
		if  := strings.LastIndex(, .ComparePart);  != -1 {
			 = [:]
		} else {
			break
		}
	}

	return len()
}

// GetTrimmedParam trims the ':' & '?' from a string
func ( string) string {
	 := 0
	 := len()

	if  == 0 || [] != paramStarterChar { // is not a param
		return 
	}
	++
	if [-1] == optionalParam { // is ?
		--
	}

	return [:]
}

// RemoveEscapeChar remove escape characters
func ( string) string {
	if strings.IndexByte(, escapeChar) != -1 {
		return strings.ReplaceAll(, string(escapeChar), "")
	}
	return 
}

func getParamConstraintType( string) TypeConstraint {
	switch  {
	case ConstraintInt:
		return intConstraint
	case ConstraintBool:
		return boolConstraint
	case ConstraintFloat:
		return floatConstraint
	case ConstraintAlpha:
		return alphaConstraint
	case ConstraintGuid:
		return guidConstraint
	case ConstraintMinLen, ConstraintMinLenLower:
		return minLenConstraint
	case ConstraintMaxLen, ConstraintMaxLenLower:
		return maxLenConstraint
	case ConstraintLen:
		return lenConstraint
	case ConstraintBetweenLen, ConstraintBetweenLenLower:
		return betweenLenConstraint
	case ConstraintMin:
		return minConstraint
	case ConstraintMax:
		return maxConstraint
	case ConstraintRange:
		return rangeConstraint
	case ConstraintDatetime:
		return datetimeConstraint
	case ConstraintRegex:
		return regexConstraint
	default:
		return noConstraint
	}
}

//nolint:errcheck // TODO: Properly check _all_ errors in here, log them & immediately return
func ( *Constraint) ( string) bool {
	var  error
	var  int

	// check data exists
	 := []TypeConstraint{minLenConstraint, maxLenConstraint, lenConstraint, minConstraint, maxConstraint, datetimeConstraint, regexConstraint}
	 := []TypeConstraint{betweenLenConstraint, rangeConstraint}

	for ,  := range  {
		if .ID ==  && len(.Data) == 0 {
			return false
		}
	}

	for ,  := range  {
		if .ID ==  && len(.Data) < 2 {
			return false
		}
	}

	// check constraints
	switch .ID {
	case noConstraint:
		// Nothing to check
	case intConstraint:
		_,  = strconv.Atoi()
	case boolConstraint:
		_,  = strconv.ParseBool()
	case floatConstraint:
		_,  = strconv.ParseFloat(, 32)
	case alphaConstraint:
		for ,  := range  {
			if !unicode.IsLetter() {
				return false
			}
		}
	case guidConstraint:
		_,  = uuid.Parse()
	case minLenConstraint:
		,  := strconv.Atoi(.Data[0])

		if len() <  {
			return false
		}
	case maxLenConstraint:
		,  := strconv.Atoi(.Data[0])

		if len() >  {
			return false
		}
	case lenConstraint:
		,  := strconv.Atoi(.Data[0])

		if len() !=  {
			return false
		}
	case betweenLenConstraint:
		,  := strconv.Atoi(.Data[0])
		,  := strconv.Atoi(.Data[1])
		 := len()
		if  <  ||  >  {
			return false
		}
	case minConstraint:
		,  := strconv.Atoi(.Data[0])
		,  = strconv.Atoi()

		if  <  {
			return false
		}
	case maxConstraint:
		,  := strconv.Atoi(.Data[0])
		,  = strconv.Atoi()

		if  >  {
			return false
		}
	case rangeConstraint:
		,  := strconv.Atoi(.Data[0])
		,  := strconv.Atoi(.Data[1])
		,  = strconv.Atoi()

		if  <  ||  >  {
			return false
		}
	case datetimeConstraint:
		_,  = time.Parse(.Data[0], )
	case regexConstraint:
		if  := .RegexCompiler.MatchString(); ! {
			return false
		}
	}

	return  == nil
}