// Copyright 2023 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package parse

import (
	"errors"
	"fmt"
	"slices"
	"strconv"
	"unicode/utf8"
)

type tokenKind int

const (
	tokenEOF tokenKind = iota
	tokenOpenBrace
	tokenCloseBrace
	tokenComma
	tokenEquals
	tokenNotEquals
	tokenMatches
	tokenNotMatches
	tokenQuoted
	tokenUnquoted
)

func (k tokenKind) String() string {
	switch k {
	case tokenOpenBrace:
		return "OpenBrace"
	case tokenCloseBrace:
		return "CloseBrace"
	case tokenComma:
		return "Comma"
	case tokenEquals:
		return "Equals"
	case tokenNotEquals:
		return "NotEquals"
	case tokenMatches:
		return "Matches"
	case tokenNotMatches:
		return "NotMatches"
	case tokenQuoted:
		return "Quoted"
	case tokenUnquoted:
		return "Unquoted"
	default:
		return "EOF"
	}
}

type token struct {
	kind  tokenKind
	value string
	position
}

// isEOF returns true if the token is an end of file token.
func (t token) isEOF() bool {
	return t.kind == tokenEOF
}

// isOneOf returns true if the token is one of the specified kinds.
func (t token) isOneOf(kinds ...tokenKind) bool {
	return slices.Contains(kinds, t.kind)
}

// unquote the value in token. If unquoted returns it unmodified.
func (t token) unquote() (string, error) {
	if t.kind == tokenQuoted {
		unquoted, err := strconv.Unquote(t.value)
		if err != nil {
			return "", err
		}
		if !utf8.ValidString(unquoted) {
			return "", errors.New("quoted string contains invalid UTF-8 code points")
		}
		return unquoted, nil
	}
	return t.value, nil
}

func (t token) String() string {
	return fmt.Sprintf("(%s) '%s'", t.kind, t.value)
}

type position struct {
	offsetStart int // The start position in the input.
	offsetEnd   int // The end position in the input.
	columnStart int // The column number.
	columnEnd   int // The end of the column.
}
