package tools

import (
	"context"
	"encoding/json"
	"fmt"
	"math"
	"strconv"
	"strings"
)

func init() {
	Register(&CalculatorTool{})
}

// ═══════════════════════════════════════════
// calculator
// ═══════════════════════════════════════════

type CalculatorTool struct{ BaseTool }

func (t *CalculatorTool) Definition() ToolDef {
	return ToolDef{
		Name:        "calculator",
		DisplayName: "Calculator",
		Description: "Evaluate mathematical expressions with precision. Supports +, -, *, /, ^ (power), % (modulo), parentheses, and functions: sqrt, abs, ceil, floor, round, log, log2, log10, sin, cos, tan, pi, e. Use this for any arithmetic, unit conversions, percentages, or calculations instead of doing mental math.",
		Category:    "utilities",
		Parameters: JSONSchema(map[string]interface{}{
			"expression": Prop("string", "Mathematical expression to evaluate, e.g. '(100 * 1.08) ^ 5' or 'sqrt(144) + log10(1000)'"),
		}, []string{"expression"}),
	}
}

func (t *CalculatorTool) Execute(ctx context.Context, execCtx ExecutionContext, argsJSON string) (string, error) {
	var args struct {
		Expression string `json:"expression"`
	}
	if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
		return "", fmt.Errorf("invalid arguments: %w", err)
	}

	if args.Expression == "" {
		return "", fmt.Errorf("expression is required")
	}

	// Normalize: replace ^ with ** for power, then rewrite for Go parser
	expr := normalizeExpr(args.Expression)

	result, err := evalExpr(expr)
	if err != nil {
		return "", fmt.Errorf("evaluation error: %w", err)
	}

	// Format result nicely
	var display string
	if result == math.Trunc(result) && !math.IsInf(result, 0) && !math.IsNaN(result) {
		display = strconv.FormatFloat(result, 'f', 0, 64)
	} else {
		display = strconv.FormatFloat(result, 'g', 15, 64)
	}

	resp := map[string]interface{}{
		"expression": args.Expression,
		"result":     result,
		"display":    display,
	}

	b, _ := json.Marshal(resp)
	return string(b), nil
}

// ── Expression Normalization ────────────────

func normalizeExpr(s string) string {
	// Replace common aliases
	s = strings.ReplaceAll(s, "×", "*")
	s = strings.ReplaceAll(s, "÷", "/")
	s = strings.ReplaceAll(s, "**", "^")
	return s
}

// ── Recursive Descent Evaluator ─────────────
// Supports: +, -, *, /, ^, %, unary -, parentheses, function calls, constants

type calcParser struct {
	input string
	pos   int
}

func evalExpr(input string) (float64, error) {
	p := &calcParser{input: strings.TrimSpace(input)}
	result, err := p.parseExpr()
	if err != nil {
		return 0, err
	}
	p.skipSpaces()
	if p.pos < len(p.input) {
		return 0, fmt.Errorf("unexpected character at position %d: %q", p.pos, string(p.input[p.pos]))
	}
	return result, nil
}

func (p *calcParser) parseExpr() (float64, error) {
	return p.parseAddSub()
}

func (p *calcParser) parseAddSub() (float64, error) {
	left, err := p.parseMulDiv()
	if err != nil {
		return 0, err
	}
	for {
		p.skipSpaces()
		if p.pos >= len(p.input) {
			break
		}
		op := p.input[p.pos]
		if op != '+' && op != '-' {
			break
		}
		p.pos++
		right, err := p.parseMulDiv()
		if err != nil {
			return 0, err
		}
		if op == '+' {
			left += right
		} else {
			left -= right
		}
	}
	return left, nil
}

func (p *calcParser) parseMulDiv() (float64, error) {
	left, err := p.parsePower()
	if err != nil {
		return 0, err
	}
	for {
		p.skipSpaces()
		if p.pos >= len(p.input) {
			break
		}
		op := p.input[p.pos]
		if op != '*' && op != '/' && op != '%' {
			break
		}
		p.pos++
		right, err := p.parsePower()
		if err != nil {
			return 0, err
		}
		switch op {
		case '*':
			left *= right
		case '/':
			if right == 0 {
				return 0, fmt.Errorf("division by zero")
			}
			left /= right
		case '%':
			if right == 0 {
				return 0, fmt.Errorf("modulo by zero")
			}
			left = math.Mod(left, right)
		}
	}
	return left, nil
}

func (p *calcParser) parsePower() (float64, error) {
	base, err := p.parseUnary()
	if err != nil {
		return 0, err
	}
	p.skipSpaces()
	if p.pos < len(p.input) && p.input[p.pos] == '^' {
		p.pos++
		// Right-associative: 2^3^2 = 2^(3^2)
		exp, err := p.parsePower()
		if err != nil {
			return 0, err
		}
		return math.Pow(base, exp), nil
	}
	return base, nil
}

func (p *calcParser) parseUnary() (float64, error) {
	p.skipSpaces()
	if p.pos < len(p.input) && p.input[p.pos] == '-' {
		p.pos++
		val, err := p.parseUnary()
		if err != nil {
			return 0, err
		}
		return -val, nil
	}
	if p.pos < len(p.input) && p.input[p.pos] == '+' {
		p.pos++
		return p.parseUnary()
	}
	return p.parsePrimary()
}

func (p *calcParser) parsePrimary() (float64, error) {
	p.skipSpaces()
	if p.pos >= len(p.input) {
		return 0, fmt.Errorf("unexpected end of expression")
	}

	// Parenthesized expression
	if p.input[p.pos] == '(' {
		p.pos++
		val, err := p.parseExpr()
		if err != nil {
			return 0, err
		}
		p.skipSpaces()
		if p.pos >= len(p.input) || p.input[p.pos] != ')' {
			return 0, fmt.Errorf("missing closing parenthesis")
		}
		p.pos++
		return val, nil
	}

	// Identifier (function or constant)
	if isAlpha(p.input[p.pos]) {
		name := p.parseIdent()
		return p.evalIdentifier(name)
	}

	// Number
	return p.parseNumber()
}

func (p *calcParser) parseIdent() string {
	start := p.pos
	for p.pos < len(p.input) && (isAlpha(p.input[p.pos]) || isDigit(p.input[p.pos])) {
		p.pos++
	}
	return strings.ToLower(p.input[start:p.pos])
}

func (p *calcParser) evalIdentifier(name string) (float64, error) {
	// Constants
	switch name {
	case "pi":
		return math.Pi, nil
	case "e":
		return math.E, nil
	case "inf":
		return math.Inf(1), nil
	}

	// Functions (require parenthesized argument)
	p.skipSpaces()
	if p.pos >= len(p.input) || p.input[p.pos] != '(' {
		return 0, fmt.Errorf("unknown constant %q (did you mean %s(x)?)", name, name)
	}
	p.pos++ // consume '('

	arg, err := p.parseExpr()
	if err != nil {
		return 0, err
	}

	// Check for second argument (e.g. pow(2, 3))
	p.skipSpaces()
	var arg2 float64
	hasArg2 := false
	if p.pos < len(p.input) && p.input[p.pos] == ',' {
		p.pos++
		arg2, err = p.parseExpr()
		if err != nil {
			return 0, err
		}
		hasArg2 = true
	}

	p.skipSpaces()
	if p.pos >= len(p.input) || p.input[p.pos] != ')' {
		return 0, fmt.Errorf("missing closing parenthesis for %s()", name)
	}
	p.pos++

	switch name {
	case "sqrt":
		return math.Sqrt(arg), nil
	case "abs":
		return math.Abs(arg), nil
	case "ceil":
		return math.Ceil(arg), nil
	case "floor":
		return math.Floor(arg), nil
	case "round":
		return math.Round(arg), nil
	case "log", "ln":
		return math.Log(arg), nil
	case "log2":
		return math.Log2(arg), nil
	case "log10":
		return math.Log10(arg), nil
	case "sin":
		return math.Sin(arg), nil
	case "cos":
		return math.Cos(arg), nil
	case "tan":
		return math.Tan(arg), nil
	case "asin":
		return math.Asin(arg), nil
	case "acos":
		return math.Acos(arg), nil
	case "atan":
		return math.Atan(arg), nil
	case "exp":
		return math.Exp(arg), nil
	case "pow":
		if !hasArg2 {
			return 0, fmt.Errorf("pow() requires two arguments: pow(base, exponent)")
		}
		return math.Pow(arg, arg2), nil
	case "max":
		if !hasArg2 {
			return 0, fmt.Errorf("max() requires two arguments")
		}
		return math.Max(arg, arg2), nil
	case "min":
		if !hasArg2 {
			return 0, fmt.Errorf("min() requires two arguments")
		}
		return math.Min(arg, arg2), nil
	default:
		return 0, fmt.Errorf("unknown function %q", name)
	}
}

func (p *calcParser) parseNumber() (float64, error) {
	start := p.pos
	if p.pos < len(p.input) && p.input[p.pos] == '.' {
		p.pos++
	}
	if p.pos >= len(p.input) || !isDigit(p.input[p.pos]) {
		if p.pos > start {
			// lone dot
			return 0, fmt.Errorf("invalid number at position %d", start)
		}
		return 0, fmt.Errorf("expected number at position %d, got %q", p.pos, string(p.input[p.pos]))
	}
	for p.pos < len(p.input) && isDigit(p.input[p.pos]) {
		p.pos++
	}
	if p.pos < len(p.input) && p.input[p.pos] == '.' {
		p.pos++
		for p.pos < len(p.input) && isDigit(p.input[p.pos]) {
			p.pos++
		}
	}
	// Scientific notation
	if p.pos < len(p.input) && (p.input[p.pos] == 'e' || p.input[p.pos] == 'E') {
		p.pos++
		if p.pos < len(p.input) && (p.input[p.pos] == '+' || p.input[p.pos] == '-') {
			p.pos++
		}
		for p.pos < len(p.input) && isDigit(p.input[p.pos]) {
			p.pos++
		}
	}

	val, err := strconv.ParseFloat(p.input[start:p.pos], 64)
	if err != nil {
		return 0, fmt.Errorf("invalid number %q", p.input[start:p.pos])
	}
	return val, nil
}

func (p *calcParser) skipSpaces() {
	for p.pos < len(p.input) && p.input[p.pos] == ' ' {
		p.pos++
	}
}

func isAlpha(b byte) bool { return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' }
func isDigit(b byte) bool { return b >= '0' && b <= '9' }

