package expr

import (
	"github.com/stephenafamo/bob"
)

type builder[B any] interface {
	New(bob.Expression) B
}

// Build an expression
func X[T bob.Expression, B builder[T]](exp bob.Expression, others ...bob.Expression) T {
	var b B

	// Easy chain. For example:
	// X("a", "=", "b")
	if len(others) > 0 {
		exp = Join{Exprs: append([]bob.Expression{exp}, others...)}
	}

	switch t := exp.(type) {
	// exp is already a chain of type T; return it without wrapping or calling New again.
	// For example, X[Expression, Expression](mysql.Quote("users", "id")) when the
	// argument is already a dialect.Expression.
	case T:
		return t
	case bob.ParensOmitter:
		if t.ShouldOmitParens() {
			break
		}

		exp = group{exp}
	default:
		exp = group{exp}
	}

	return b.New(exp)
}

// prefix the expression with a NOT
func Not[T bob.Expression, B builder[T]](exp bob.Expression) T {
	var b B
	return b.New(Join{Exprs: []bob.Expression{not, X[T, B](exp)}})
}

// Exists expression
func Exists[T bob.Expression, B builder[T]](exp bob.Expression) T {
	var b B
	return b.New(Join{Exprs: []bob.Expression{exists, group{exp}}})
}

// prefix the expression with a - (minus)
func Minus[T bob.Expression, B builder[T]](exp bob.Expression) T {
	var b B
	return b.New(Join{Exprs: []bob.Expression{minus, X[T, B](exp)}})
}

// ANY expression
func Any[T bob.Expression, B builder[T]](exp bob.Expression) T {
	var b B
	return b.New(Join{Exprs: []bob.Expression{anyOp, group{exp}}})
}

// ALL expression
func All[T bob.Expression, B builder[T]](exp bob.Expression) T {
	var b B
	return b.New(Join{Exprs: []bob.Expression{all, group{exp}}})
}

// To be embedded in query mods
// T is the chain type, this allows dialects to have custom chain methods
// F is function type, so that the dialect can change where it
// accepted. E.g. it can be modified to work as a mod
// B has a New() method that is used to create a new instance of T
type Builder[T bob.Expression, B builder[T]] struct{}

// prefix the expression with a NOT
func (e Builder[T, B]) Not(exp bob.Expression) T {
	return Not[T, B](exp)
}

// Or
func (e Builder[T, B]) Or(args ...bob.Expression) T {
	return X[T, B](Join{Exprs: args, Sep: " OR "})
}

// And
func (e Builder[T, B]) And(args ...bob.Expression) T {
	return X[T, B](Join{Exprs: args, Sep: " AND "})
}

// single quoted raw string
func (e Builder[T, B]) S(s string) T {
	return X[T, B](rawString(s))
}

// Comma separated list of arguments
func (e Builder[T, B]) Arg(vals ...any) T {
	return X[T, B](Arg(vals...))
}

// Comma separated list of arguments surrounded by parentheses
func (e Builder[T, B]) ArgGroup(vals ...any) T {
	return X[T, B](ArgGroup(vals...))
}

func (e Builder[T, B]) Placeholder(n uint) T {
	return e.Arg(make([]any, n)...)
}

func (e Builder[T, B]) Raw(query string, args ...any) T {
	return X[T, B](Clause{
		query: query,
		args:  args,
	})
}

// Add parentheses around an expressions and separate them by commas
func (e Builder[T, B]) Group(exps ...bob.Expression) T {
	return X[T, B](group(exps))
}

// quoted and joined... something like "users"."id"
func (e Builder[T, B]) Quote(aa ...string) T {
	return X[T, B](Quote(aa...))
}

// quoted and joined... something like "users"."id"
func (e Builder[T, B]) Cast(exp bob.Expression, typname string) T {
	return X[T, B](Cast(exp, typname))
}

// EXISTS expression
func (e Builder[T, B]) Exists(exp bob.Expression) T {
	return Exists[T, B](exp)
}

// prefix the expression with a - (minus)
func (e Builder[T, B]) Minus(exp bob.Expression) T {
	return Minus[T, B](exp)
}

// ANY expression
func (e Builder[T, B]) Any(exp bob.Expression) T {
	return Any[T, B](exp)
}

// ALL expression
func (e Builder[T, B]) All(exp bob.Expression) T {
	return All[T, B](exp)
}
