package builderimp

import (
	"reflect"

	"github.com/tech/sendico/pkg/db/repository/builder"
	"go.mongodb.org/mongo-driver/v2/bson"
)

type literalExpression struct {
	value any
}

func NewLiteralExpression(value any) builder.Expression {
	return &literalExpression{value: value}
}

func (e *literalExpression) Build() any {
	return bson.D{{Key: string(builder.Literal), Value: e.value}}
}

type variadicExpression struct {
	op    builder.MongoOperation
	parts []builder.Expression
}

func (e *variadicExpression) Build() any {
	args := make([]any, 0, len(e.parts))
	for _, p := range e.parts {
		args = append(args, p.Build())
	}
	return bson.D{{Key: string(e.op), Value: args}}
}

func newVariadicExpression(op builder.MongoOperation, exprs ...builder.Expression) builder.Expression {
	return &variadicExpression{
		op:    op,
		parts: exprs,
	}
}

func newBinaryExpression(op builder.MongoOperation, left, right builder.Expression) builder.Expression {
	return &variadicExpression{
		op:    op,
		parts: []builder.Expression{left, right},
	}
}

type unaryExpression struct {
	op  builder.MongoOperation
	rhs builder.Expression
}

func (e *unaryExpression) Build() any {
	return bson.D{{Key: string(e.op), Value: e.rhs.Build()}}
}

func newUnaryExpression(op builder.MongoOperation, right builder.Expression) builder.Expression {
	return &unaryExpression{
		op:  op,
		rhs: right,
	}
}

type matchExpression struct {
	op  builder.MongoOperation
	rhs builder.Expression
}

func (e *matchExpression) Build() any {
	return bson.E{Key: string(e.op), Value: e.rhs.Build()}
}

func newMatchExpression(op builder.MongoOperation, right builder.Expression) builder.Expression {
	return &matchExpression{
		op:  op,
		rhs: right,
	}
}

func InRef(value builder.Field) builder.Expression {
	return newMatchExpression(builder.In, NewValue(NewRefFieldImp(value).Build()))
}

type inImpl struct {
	values []any
}

func (e *inImpl) Build() any {
	return bson.D{{Key: string(builder.In), Value: e.values}}
}

func In(values ...any) builder.Expression {
	var flattenedValues []any

	for _, v := range values {
		switch reflect.TypeOf(v).Kind() {
		case reflect.Slice:
			slice := reflect.ValueOf(v)
			for i := range slice.Len() {
				flattenedValues = append(flattenedValues, slice.Index(i).Interface())
			}
		default:
			flattenedValues = append(flattenedValues, v)
		}
	}
	return &inImpl{values: flattenedValues}
}
