package analytics

import (
	"fmt"
	"strings"
	"unicode"
)

// TokenType represents the type of a lexical token in the formula DSL.
type TokenType int

const (
	// Literals
	TokNumber     TokenType = iota // 123, 3.14
	TokString                      // 'hello', "hello"
	TokIdentifier                  // FieldName, Parent__r.Name
	TokBoolean                     // TRUE, FALSE

	// Operators
	TokPlus      // +
	TokMinus     // -
	TokStar      // *
	TokSlash     // /
	TokCaret     // ^ (exponent)
	TokAmpersand // & (string concat, SF style)
	TokEQ        // =, ==
	TokNE        // <>, !=
	TokLT        // <
	TokLE        // <=
	TokGT        // >
	TokGE        // >=

	// Logical
	TokAnd // &&, AND
	TokOr  // ||, OR
	TokNot // !, NOT

	// Delimiters
	TokLParen // (
	TokRParen // )
	TokComma  // ,
	TokDot    // .

	// Special
	TokEOF
)

var tokenNames = map[TokenType]string{
	TokNumber: "Number", TokString: "String", TokIdentifier: "Identifier",
	TokBoolean: "Boolean", TokPlus: "+", TokMinus: "-", TokStar: "*",
	TokSlash: "/", TokCaret: "^", TokAmpersand: "&", TokEQ: "=",
	TokNE: "!=", TokLT: "<", TokLE: "<=", TokGT: ">", TokGE: ">=",
	TokAnd: "AND", TokOr: "OR", TokNot: "NOT",
	TokLParen: "(", TokRParen: ")", TokComma: ",", TokDot: ".",
	TokEOF: "EOF",
}

func (t TokenType) String() string {
	if s, ok := tokenNames[t]; ok {
		return s
	}
	return fmt.Sprintf("Token(%d)", int(t))
}

// Token is a single lexical token.
type Token struct {
	Type  TokenType
	Value string // raw text
	Pos   int    // position in source
}

func (t Token) String() string {
	return fmt.Sprintf("{%s %q @%d}", t.Type, t.Value, t.Pos)
}

// Tokenize breaks a formula DSL string into tokens.
func Tokenize(input string) ([]Token, error) {
	var tokens []Token
	runes := []rune(input)
	i := 0
	n := len(runes)

	for i < n {
		ch := runes[i]

		// Skip whitespace
		if unicode.IsSpace(ch) {
			i++
			continue
		}

		// Skip block comments /* ... */
		if ch == '/' && i+1 < n && runes[i+1] == '*' {
			i += 2
			for i+1 < n {
				if runes[i] == '*' && runes[i+1] == '/' {
					i += 2
					break
				}
				i++
			}
			continue
		}

		pos := i

		// String literal (single or double quoted)
		if ch == '\'' || ch == '"' {
			tok, end, err := lexString(runes, i)
			if err != nil {
				return nil, err
			}
			tokens = append(tokens, Token{TokString, tok, pos})
			i = end
			continue
		}

		// Number
		if unicode.IsDigit(ch) || (ch == '.' && i+1 < n && unicode.IsDigit(runes[i+1])) {
			tok, end := lexNumber(runes, i)
			tokens = append(tokens, Token{TokNumber, tok, pos})
			i = end
			continue
		}

		// Identifier or keyword
		if unicode.IsLetter(ch) || ch == '_' {
			tok, end := lexIdentifier(runes, i)
			upper := strings.ToUpper(tok)
			switch upper {
			case "TRUE", "FALSE":
				tokens = append(tokens, Token{TokBoolean, upper, pos})
			case "AND":
				tokens = append(tokens, Token{TokAnd, "AND", pos})
			case "OR":
				tokens = append(tokens, Token{TokOr, "OR", pos})
			case "NOT":
				tokens = append(tokens, Token{TokNot, "NOT", pos})
			default:
				tokens = append(tokens, Token{TokIdentifier, tok, pos})
			}
			i = end
			continue
		}

		// Two-char operators
		if i+1 < n {
			two := string(runes[i : i+2])
			switch two {
			case "<=":
				tokens = append(tokens, Token{TokLE, "<=", pos})
				i += 2
				continue
			case ">=":
				tokens = append(tokens, Token{TokGE, ">=", pos})
				i += 2
				continue
			case "<>":
				tokens = append(tokens, Token{TokNE, "<>", pos})
				i += 2
				continue
			case "!=":
				tokens = append(tokens, Token{TokNE, "!=", pos})
				i += 2
				continue
			case "==":
				tokens = append(tokens, Token{TokEQ, "==", pos})
				i += 2
				continue
			case "&&":
				tokens = append(tokens, Token{TokAnd, "&&", pos})
				i += 2
				continue
			case "||":
				tokens = append(tokens, Token{TokOr, "||", pos})
				i += 2
				continue
			}
		}

		// Single-char operators/delimiters
		switch ch {
		case '+':
			tokens = append(tokens, Token{TokPlus, "+", pos})
		case '-':
			tokens = append(tokens, Token{TokMinus, "-", pos})
		case '*':
			tokens = append(tokens, Token{TokStar, "*", pos})
		case '/':
			tokens = append(tokens, Token{TokSlash, "/", pos})
		case '^':
			tokens = append(tokens, Token{TokCaret, "^", pos})
		case '&':
			tokens = append(tokens, Token{TokAmpersand, "&", pos})
		case '=':
			tokens = append(tokens, Token{TokEQ, "=", pos})
		case '<':
			tokens = append(tokens, Token{TokLT, "<", pos})
		case '>':
			tokens = append(tokens, Token{TokGT, ">", pos})
		case '!':
			tokens = append(tokens, Token{TokNot, "!", pos})
		case '(':
			tokens = append(tokens, Token{TokLParen, "(", pos})
		case ')':
			tokens = append(tokens, Token{TokRParen, ")", pos})
		case ',':
			tokens = append(tokens, Token{TokComma, ",", pos})
		case '.':
			tokens = append(tokens, Token{TokDot, ".", pos})
		default:
			return nil, fmt.Errorf("unexpected character %q at position %d", string(ch), pos)
		}
		i++
	}

	tokens = append(tokens, Token{TokEOF, "", len(runes)})
	return tokens, nil
}

func lexString(runes []rune, start int) (string, int, error) {
	quote := runes[start]
	var buf strings.Builder
	i := start + 1
	for i < len(runes) {
		if runes[i] == '\\' && i+1 < len(runes) {
			buf.WriteRune(runes[i+1])
			i += 2
			continue
		}
		if runes[i] == quote {
			return buf.String(), i + 1, nil
		}
		buf.WriteRune(runes[i])
		i++
	}
	return "", 0, fmt.Errorf("unterminated string starting at position %d", start)
}

func lexNumber(runes []rune, start int) (string, int) {
	i := start
	hasDot := false
	for i < len(runes) {
		ch := runes[i]
		if unicode.IsDigit(ch) {
			i++
		} else if ch == '.' && !hasDot {
			hasDot = true
			i++
		} else {
			break
		}
	}
	return string(runes[start:i]), i
}

func lexIdentifier(runes []rune, start int) (string, int) {
	i := start
	for i < len(runes) && (unicode.IsLetter(runes[i]) || unicode.IsDigit(runes[i]) || runes[i] == '_') {
		i++
	}
	// Handle SF polymorphic relationship syntax: Owner:User → Owner__User
	// The colon is followed by another identifier (the target object type).
	if i < len(runes) && runes[i] == ':' && i+1 < len(runes) && (unicode.IsLetter(runes[i+1]) || runes[i+1] == '_') {
		i++ // consume ':'
		for i < len(runes) && (unicode.IsLetter(runes[i]) || unicode.IsDigit(runes[i]) || runes[i] == '_') {
			i++
		}
	}
	return string(runes[start:i]), i
}
