package utils

import (
	"fmt"
	"strconv"
	"strings"
)

// ParseParams splits a line into space-separated parameters, respecting quoted strings
func ParseParams(s string) ([]string, error) {
	s = strings.TrimSpace(s)
	if s == "" {
		return []string{}, nil
	}

	var params []string
	var current strings.Builder
	inString := false

	for i := 0; i < len(s); i++ {
		ch := s[i]

		if ch == '"' {
			inString = !inString
			current.WriteByte(ch)
			continue
		}

		if !inString && (ch == ' ' || ch == '\t') {
			if current.Len() > 0 {
				params = append(params, current.String())
				current.Reset()
			}
		} else {
			current.WriteByte(ch)
		}
	}

	if current.Len() > 0 {
		params = append(params, current.String())
	}

	if inString {
		return nil, fmt.Errorf("unterminated string")
	}

	return params, nil
}

// NormalizeSpaces reduces multiple spaces to single space, respecting quoted strings
func NormalizeSpaces(s string) string {
	s = strings.TrimSpace(s)
	var result strings.Builder
	inString := false
	lastWasSpace := false

	for i := 0; i < len(s); i++ {
		ch := s[i]

		if ch == '"' {
			inString = !inString
			result.WriteByte(ch)
			lastWasSpace = false
			continue
		}

		if !inString {
			if ch == ' ' || ch == '\t' {
				if !lastWasSpace {
					result.WriteByte(' ')
					lastWasSpace = true
				}
			} else {
				result.WriteByte(ch)
				lastWasSpace = false
			}
		} else {
			result.WriteByte(ch)
			lastWasSpace = false
		}
	}

	return result.String()
}

// IsStringLiteral checks if s is a quoted string
func IsStringLiteral(s string) bool {
	l := len(s)
	return l >= 2 && s[0] == '"' && s[l-1] == '"'
}

// StripQuotes removes surrounding quotes from a string literal
func StripQuotes(s string) string {
	if IsStringLiteral(s) {
		return s[1 : len(s)-1]
	}
	return s
}

// ToUpper converts string to uppercase
func ToUpper(s string) string {
	return strings.ToUpper(s)
}

// NormalizeCommas inserts spaces around commas outside quoted strings,
// then collapses multiple spaces. This ensures commas are treated as
// separate tokens when passed to ParseParams.
func NormalizeCommas(s string) string {
	var result strings.Builder
	inString := false

	for i := 0; i < len(s); i++ {
		ch := s[i]

		if ch == '"' {
			inString = !inString
			result.WriteByte(ch)
			continue
		}

		if !inString && ch == ',' {
			result.WriteByte(' ')
			result.WriteByte(',')
			result.WriteByte(' ')
		} else {
			result.WriteByte(ch)
		}
	}

	return NormalizeSpaces(result.String())
}

// ValidateIdentifier checks if s is a valid identifier (starts with letter/underscore, continues with alphanumeric/underscore)
func ValidateIdentifier(s string) bool {
	if len(s) == 0 {
		return false
	}

	first := s[0]
	if !((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z') || first == '_') {
		return false
	}

	for i := 1; i < len(s); i++ {
		ch := s[i]
		if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_') {
			return false
		}
	}

	return true
}

// ConstantLookup is a function type for looking up constant values by name
type ConstantLookup func(name string) (value int64, found bool)

// EvaluateExpression evaluates a simple left-to-right expression
// Supports:
//   - Decimal: 123
//   - Hex: $FF
//   - Binary: !11111111
//   - Constants: MAXVAL (via lookup function)
//   - Operators: +, -, *, /, | (binary OR), & (binary AND)
//
// Returns value and error. If lookup is nil, constants are not supported.
func EvaluateExpression(expr string, lookup ConstantLookup) (int64, error) {
	expr = strings.TrimSpace(expr)
	if expr == "" {
		return 0, fmt.Errorf("empty expression")
	}

	// Split expression into terms and operators
	var terms []string
	var operators []rune
	var currentTerm strings.Builder

	for i := 0; i < len(expr); i++ {
		ch := expr[i]

		if isOperator(ch) {
			// Save current term
			if currentTerm.Len() > 0 {
				terms = append(terms, strings.TrimSpace(currentTerm.String()))
				currentTerm.Reset()
			}
			operators = append(operators, rune(ch))
		} else {
			currentTerm.WriteByte(ch)
		}
	}

	// Save final term
	if currentTerm.Len() > 0 {
		terms = append(terms, strings.TrimSpace(currentTerm.String()))
	}

	if len(terms) == 0 {
		return 0, fmt.Errorf("no terms in expression")
	}

	if len(operators) != len(terms)-1 {
		return 0, fmt.Errorf("mismatched operators and terms")
	}

	// Evaluate first term
	result, err := evaluateTerm(terms[0], lookup)
	if err != nil {
		return 0, fmt.Errorf("term %q: %w", terms[0], err)
	}

	// Apply operators left-to-right
	for i, op := range operators {
		nextVal, err := evaluateTerm(terms[i+1], lookup)
		if err != nil {
			return 0, fmt.Errorf("term %q: %w", terms[i+1], err)
		}

		switch op {
		case '+':
			result = result + nextVal
		case '-':
			result = result - nextVal
		case '*':
			result = result * nextVal
		case '/':
			if nextVal == 0 {
				return 0, fmt.Errorf("division by zero")
			}
			result = result / nextVal
		case '|':
			result = result | nextVal
		case '&':
			result = result & nextVal
		case '^':
			result = result ^ nextVal
		default:
			return 0, fmt.Errorf("unknown operator %q", op)
		}
	}

	return result, nil
}

// isOperator checks if a character is an operator
func isOperator(ch byte) bool {
	return ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '|' || ch == '&'
}

// evaluateTerm evaluates a single term (number or constant)
func evaluateTerm(term string, lookup ConstantLookup) (int64, error) {
	term = strings.TrimSpace(term)
	if term == "" {
		return 0, fmt.Errorf("empty term")
	}

	// Check for hex: $FF
	if strings.HasPrefix(term, "$") {
		val, err := strconv.ParseInt(term[1:], 16, 64)
		if err != nil {
			return 0, fmt.Errorf("invalid hex number: %w", err)
		}
		return val, nil
	}

	// Check for binary: %11111111
	if strings.HasPrefix(term, "%") {
		val, err := strconv.ParseInt(term[1:], 2, 64)
		if err != nil {
			return 0, fmt.Errorf("invalid binary number: %w", err)
		}
		return val, nil
	}

	// Check for identifier (constant)
	first := term[0]
	if (first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z') || first == '_' {
		if lookup == nil {
			return 0, fmt.Errorf("constant %q not supported (no lookup function)", term)
		}
		val, found := lookup(term)
		if !found {
			return 0, fmt.Errorf("constant %q not found", term)
		}
		return val, nil
	}

	// Decimal number
	val, err := strconv.ParseInt(term, 10, 64)
	if err != nil {
		return 0, fmt.Errorf("invalid decimal number: %w", err)
	}
	return val, nil
}
