package soql

import (
	"fmt"
	"strings"
)

type Node interface {
	String() string
}

type Expr interface {
	Node
	exprNode()
}

type Identifier struct {
	Value string
}

func (i *Identifier) exprNode()      {}
func (i *Identifier) String() string { return i.Value }

type StringLiteral struct {
	Value string
}

func (s *StringLiteral) exprNode() {}
func (s *StringLiteral) String() string {
	escaped := strings.ReplaceAll(s.Value, "'", "\\'")
	return fmt.Sprintf("'%s'", escaped)
}

type NumberLiteral struct {
	Value string
}

func (n *NumberLiteral) exprNode()      {}
func (n *NumberLiteral) String() string { return n.Value }

type BooleanLiteral struct {
	Value bool
}

func (b *BooleanLiteral) exprNode() {}
func (b *BooleanLiteral) String() string {
	if b.Value {
		return "true"
	}
	return "false"
}

type NullLiteral struct{}

func (n *NullLiteral) exprNode()      {}
func (n *NullLiteral) String() string { return "null" }

type ParenExpr struct {
	Expr Expr
}

func (p *ParenExpr) exprNode() {}
func (p *ParenExpr) String() string {
	return fmt.Sprintf("( %s )", p.Expr.String())
}

type ListLiteral struct {
	Elements []Expr
}

func (l *ListLiteral) exprNode() {}
func (l *ListLiteral) String() string {
	var sb strings.Builder
	sb.WriteString("[")
	for i, el := range l.Elements {
		if i > 0 {
			sb.WriteString(", ")
		}
		sb.WriteString(el.String())
	}
	sb.WriteString("]")
	return sb.String()
}

type UnaryExpr struct {
	Operator string
	Right    Expr
}

func (u *UnaryExpr) exprNode() {}
func (u *UnaryExpr) String() string {
	op := strings.ToUpper(u.Operator)
	if op == "NOT" {
		return fmt.Sprintf("not %s", u.Right.String())
	}
	return fmt.Sprintf("%s%s", op, u.Right.String())
}

type BinaryExpr struct {
	Left     Expr
	Operator string
	Right    Expr
}

func (b *BinaryExpr) exprNode() {}
func (b *BinaryExpr) String() string {
	op := strings.ToUpper(b.Operator)

	if op == "LIKE" || op == "NOT LIKE" {
		rightStr := b.Right.String()
		if len(rightStr) >= 2 && rightStr[0] == '\'' && rightStr[len(rightStr)-1] == '\'' {
			inner := rightStr[1 : len(rightStr)-1]
			var out string
			if strings.HasPrefix(inner, "%") && strings.HasSuffix(inner, "%") {
				out = fmt.Sprintf("%s contains '%s'", b.Left.String(), inner[1:len(inner)-1])
			} else if strings.HasPrefix(inner, "%") {
				out = fmt.Sprintf("%s endsWith '%s'", b.Left.String(), inner[1:])
			} else if strings.HasSuffix(inner, "%") {
				out = fmt.Sprintf("%s startsWith '%s'", b.Left.String(), inner[:len(inner)-1])
			} else {
				out = fmt.Sprintf("%s == '%s'", b.Left.String(), inner)
			}

			if op == "NOT LIKE" {
				return fmt.Sprintf("not (%s)", out)
			}
			return out
		}
		// Fallback
		op = strings.ToLower(op)
	} else {
		switch op {
		case "=":
			op = "=="
		case "<>":
			op = "!="
		default:
			op = strings.ToLower(op)
		}
	}

	return fmt.Sprintf("%s %s %s", b.Left.String(), op, b.Right.String())
}

type CallExpr struct {
	Function string
	Args     []Expr
}

func (c *CallExpr) exprNode() {}
func (c *CallExpr) String() string {
	var sb strings.Builder
	sb.WriteString(c.Function)
	sb.WriteString("(")
	for i, arg := range c.Args {
		if i > 0 {
			sb.WriteString(", ")
		}
		sb.WriteString(arg.String())
	}
	sb.WriteString(")")
	return sb.String()
}
