package querycodec

import (
	"fmt"
	"strings"
	"time"
)

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 {
	return fmt.Sprintf("'%s'", EscapeStringValue(s.Value))
}

// EscapeStringValue escapes a raw string value for embedding inside the
// formula language's single-quoted string literal. Shared by StringLiteral
// (parser AST output) and the semi-join resolver (post-resolution ID list)
// so both honor the same escape contract.
//
// Backslash MUST be escaped before single-quote: the formula evaluator
// (expr-lang) treats `\` as an escape character, so a raw `\` followed by
// any character would otherwise consume the next character (e.g. `a\'b`
// would re-parse as escaped quote rather than literal `\` + literal `'`).
func EscapeStringValue(value string) string {
	value = strings.ReplaceAll(value, "\\", "\\\\")
	return strings.ReplaceAll(value, "'", "\\'")
}

type NumberLiteral struct {
	Value string
}

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

type DateTimeLiteral struct {
	Value string
}

func (d *DateTimeLiteral) exprNode() {}
func (d *DateTimeLiteral) String() string {
	return fmt.Sprintf("'%s'", EscapeStringValue(normalizeDateTimeLiteral(d.Value)))
}

func normalizeDateTimeLiteral(value string) string {
	if !strings.Contains(value, "T") {
		return value
	}
	parsed, err := time.Parse(time.RFC3339Nano, value)
	if err != nil {
		return value
	}
	return parsed.UTC().Format("2006-01-02 15:04:05")
}

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()
}

// SemiJoinPlaceholder serializes a parsed `IN (SELECT ...)` subquery as a
// stable single-element list literal whose only element is the marker string
// `__nx_semijoin_<Index>__`. Resolver substitutes this entire bracketed
// segment with the literal IDs returned by executing the inner query.
type SemiJoinPlaceholder struct {
	Index int
}

func (s *SemiJoinPlaceholder) exprNode() {}
func (s *SemiJoinPlaceholder) String() string {
	return SemiJoinPlaceholderBracketed(s.Index)
}

// SemiJoinPlaceholderToken returns the stable marker string used inside the
// FilterExpr placeholder. Single source of truth — both the parser AST output
// and the resolver substitution helper derive their substrings from this.
func SemiJoinPlaceholderToken(index int) string {
	return fmt.Sprintf("__nx_semijoin_%d__", index)
}

// SemiJoinPlaceholderBracketed returns the full bracketed list-literal form
// the parser emits into FilterExpr (e.g. `['__nx_semijoin_0__']`). The
// resolver matches and replaces this exact substring; keeping construction
// in one place prevents the parser/resolver contract from silently drifting.
func SemiJoinPlaceholderBracketed(index int) string {
	return fmt.Sprintf("['%s']", SemiJoinPlaceholderToken(index))
}

// SemiJoinPlaceholderPrefix is the substring guard used to assert that no
// unresolved semi-join placeholders leak past resolution into SQL emission.
const SemiJoinPlaceholderPrefix = "__nx_semijoin_"

// RelativeDateLiteral is resolved by the L1 execution boundary, never as a field.
type RelativeDateLiteral struct{ Value string }

func (d *RelativeDateLiteral) exprNode()      {}
func (d *RelativeDateLiteral) String() string { return d.Value }
