package logicalplan

import (
	
	

	
)

type Builder struct {
	plan *LogicalPlan
	err  error
}

func ( Builder) (
	 TableProvider,
	 string,
) Builder {
	return Builder{
		err: .err,
		plan: &LogicalPlan{
			TableScan: &TableScan{
				TableProvider: ,
				TableName:     ,
			},
		},
	}
}

func ( Builder) (
	 TableProvider,
	 string,
) Builder {
	return Builder{
		err: .err,
		plan: &LogicalPlan{
			SchemaScan: &SchemaScan{
				TableProvider: ,
				TableName:     ,
			},
		},
	}
}

func ( Builder) (
	 ...Expr,
) Builder {
	return Builder{
		err: .err,
		plan: &LogicalPlan{
			Input: .plan,
			Projection: &Projection{
				Exprs: ,
			},
		},
	}
}

type Visitor interface {
	PreVisit(expr Expr) bool
	Visit(expr Expr) bool
	PostVisit(expr Expr) bool
}

type ExprTypeFinder interface {
	DataTypeForExpr(expr Expr) (arrow.DataType, error)
}

type Expr interface {
	DataType(ExprTypeFinder) (arrow.DataType, error)
	Accept(Visitor) bool
	Name() string
	Equal(Expr) bool
	fmt.Stringer

	// ColumnsUsedExprs extracts all the expressions that are used that cause
	// physical data to be read from a column.
	ColumnsUsedExprs() []Expr

	// MatchColumn returns whether it would operate on the passed column. In
	// contrast to the ColumnUsedExprs function from the Expr interface, it is not
	// useful to identify which columns are to be read physically. This is
	// necessary to distinguish between projections.
	//
	// Take the example of a column that projects `XYZ > 0`. Matcher can be
	// used to identify the column in the resulting Apache Arrow frames, while
	// ColumnsUsed will return `XYZ` to be necessary to be loaded physically.
	MatchColumn(columnName string) bool

	// MatchPath returns whether it would operate on the passed path. This is nessesary for nested schemas.
	MatchPath(path string) bool

	// Computed returns whether the expression is computed as opposed to being
	// a static value or unmodified physical column.
	Computed() bool

	// Clone returns a deep copy of the expression.
	Clone() Expr
}

func ( Builder) ( Expr) Builder {
	if  == nil {
		return 
	}

	return Builder{
		err: .err,
		plan: &LogicalPlan{
			Input: .plan,
			Filter: &Filter{
				Expr: ,
			},
		},
	}
}

func ( Builder) (
	 ...Expr,
) Builder {
	return Builder{
		err: .err,
		plan: &LogicalPlan{
			Distinct: &Distinct{
				Exprs: ,
			},
			Input: &LogicalPlan{
				Projection: &Projection{
					Exprs: ,
				},
				Input: .plan,
			},
		},
	}
}

func ( Builder) ( Expr) Builder {
	if  == nil {
		return 
	}

	return Builder{
		err: .err,
		plan: &LogicalPlan{
			Input: .plan,
			Limit: &Limit{
				Expr: ,
			},
		},
	}
}

func ( Builder) (
	 []*AggregationFunction,
	 []Expr,
) Builder {
	 := make([]*AggregationFunction, 0, len())
	 := make([]Expr, 0, len())
	 := false

	var  error
	for ,  := range  {
		, , ,  := resolveAggregation(.plan, )
		if  != nil {
			 = errors.Join(, )
		}

		if  {
			 = true
		}

		 = append(, ...)
		 = append(, ...)
	}

	if ! {
		return Builder{
			err: ,
			plan: &LogicalPlan{
				Aggregation: &Aggregation{
					GroupExprs: ,
					AggExprs:   ,
				},
				Input: .plan,
			},
		}
	}

	return Builder{
		err: ,
		plan: &LogicalPlan{
			Projection: &Projection{
				Exprs: append(, ...),
			},
			Input: &LogicalPlan{
				Aggregation: &Aggregation{
					GroupExprs: ,
					AggExprs:   ,
				},
				Input: .plan,
			},
		},
	}
}

func resolveAggregation( *LogicalPlan,  *AggregationFunction) ([]*AggregationFunction, []Expr, bool, error) {
	switch .Func {
	case AggFuncAvg:
		 := &AggregationFunction{
			Func: AggFuncSum,
			Expr: .Expr,
		}
		 := &AggregationFunction{
			Func: AggFuncCount,
			Expr: .Expr,
		}

		var (
			 Expr = 
			   arrow.DataType
		)
		,  := .Expr.DataType()
		// intentionally not handling the error here, as it will be handled
		// in the build function.
		if !arrow.TypeEqual(, arrow.PrimitiveTypes.Int64) {
			 = Convert(, )
		}

		 := (&BinaryExpr{
			Left:  ,
			Op:    OpDiv,
			Right: ,
		}).Alias(.String())

		return []*AggregationFunction{, }, []Expr{}, true, 
	default:
		return []*AggregationFunction{}, []Expr{}, false, nil
	}
}

func ( Builder) (,  Expr) Builder {
	if  == nil ||  == nil {
		return 
	}

	return Builder{
		err: .err,
		plan: &LogicalPlan{
			Input: .plan,
			Sample: &Sample{
				Expr:  ,
				Limit: ,
			},
		},
	}
}

func ( Builder) () (*LogicalPlan, error) {
	if .err != nil {
		return nil, .err
	}

	if  := Validate(.plan);  != nil {
		return nil, 
	}
	return .plan, nil
}