package builderimp

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

// projectionExprImp is a concrete implementation of builder.Projection
// that projects a field using a custom expression.
type projectionExprImp struct {
	expr  builder.Expression // The expression for this projection.
	field builder.Field      // The field name for the projected field.
}

// Field returns the field being projected.
func (p *projectionExprImp) Field() builder.Field {
	return p.field
}

// Expression returns the expression for the projection.
func (p *projectionExprImp) Expression() builder.Expression {
	return p.expr
}

// Build returns the built expression. If no expression is provided, returns 1.
func (p *projectionExprImp) Build() bson.D {
	if p.expr == nil {
		return bson.D{{Key: p.field.Build(), Value: 1}}
	}
	return bson.D{{Key: p.field.Build(), Value: p.expr.Build()}}
}

// NewProjectionExpr creates a new Projection for a given field and expression.
func NewProjectionExpr(field builder.Field, expr builder.Expression) builder.Projection {
	return &projectionExprImp{field: field, expr: expr}
}

// aliasProjectionImp is a concrete implementation of builder.Projection
// that projects an alias (renaming a field or expression).
type aliasProjectionImp struct {
	alias builder.Alias // The alias for this projection.
}

// Field returns the field being projected (via the alias).
func (p *aliasProjectionImp) Field() builder.Field {
	return p.alias.Field()
}

// Expression returns no additional expression for an alias projection.
func (p *aliasProjectionImp) Expression() builder.Expression {
	return nil
}

// Build returns the built alias expression.
func (p *aliasProjectionImp) Build() bson.D {
	return p.alias.Build()
}

// NewAliasProjection creates a new Projection that renames or wraps an existing field or expression.
func NewAliasProjection(alias builder.Alias) builder.Projection {
	return &aliasProjectionImp{alias: alias}
}

// sinkProjectionImp is a simple include/exclude projection (0 or 1).
type sinkProjectionImp struct {
	field builder.Field // The field name for the projected field.
	val   int           // 1 to include, 0 to exclude.
}

// Expression returns no expression for a sink projection.
func (p *sinkProjectionImp) Expression() builder.Expression {
	return nil
}

// Build returns the include/exclude projection.
func (p *sinkProjectionImp) Build() bson.D {
	return bson.D{{Key: p.field.Build(), Value: p.val}}
}

// NewSinkProjection creates a new Projection that includes (true) or excludes (false) a field.
func NewSinkProjection(field builder.Field, include bool) builder.Projection {
	val := 0
	if include {
		val = 1
	}
	return &sinkProjectionImp{field: field, val: val}
}

// IncludeField returns a projection including the given field.
func IncludeField(field builder.Field) builder.Projection {
	return NewSinkProjection(field, true)
}

// ExcludeField returns a projection excluding the given field.
func ExcludeField(field builder.Field) builder.Projection {
	return NewSinkProjection(field, false)
}
