package parser

import (
	"github.com/chaisql/chai/internal/expr"
	"github.com/chaisql/chai/internal/query/statement"
	"github.com/chaisql/chai/internal/sql/scanner"
	"github.com/cockroachdb/errors"
)

// parseSelectStatement parses a select string and returns a Statement AST row.
// This function assumes the SELECT token has already been consumed.
func (p *Parser) parseSelectStatement() (*statement.SelectStmt, error) {
	var stmt statement.SelectStmt

	// Parse SELECT ... [UNION | UNION ALL | INTERSECT] SELECT ...
	err := p.parseCompoundSelectStatement(&stmt)
	if err != nil {
		return nil, err
	}

	// Parse order by: "ORDER BY path [ASC|DESC]?"
	stmt.OrderBy, stmt.OrderByDirection, err = p.parseOrderBy()
	if err != nil {
		return nil, err
	}

	// Parse limit: "LIMIT expr"
	stmt.LimitExpr, err = p.parseLimit()
	if err != nil {
		return nil, errors.Wrap(err, "failed to parse LIMIT clause")
	}

	// Parse offset: "OFFSET expr"
	stmt.OffsetExpr, err = p.parseOffset()
	if err != nil {
		return nil, errors.Wrap(err, "failed to parse OFFSET clause")
	}

	return &stmt, nil
}

func (p *Parser) parseCompoundSelectStatement(stmt *statement.SelectStmt) error {
	for {
		core, err := p.parseSelectCore()
		if err != nil {
			return err
		}

		// Parse optional compound operator
		tok, _, _ := p.ScanIgnoreWhitespace()
		if tok == scanner.UNION {
			all, err := p.parseOptional(scanner.ALL)
			if err != nil {
				return err
			}
			if all {
				tok = scanner.ALL
			}
		}

		stmt.CompoundSelect = append(stmt.CompoundSelect, core)

		if tok != scanner.UNION && tok != scanner.ALL {
			p.Unscan()
			break
		}

		stmt.CompoundOperators = append(stmt.CompoundOperators, tok)
	}

	return nil
}

func (p *Parser) parseSelectCore() (*statement.SelectCoreStmt, error) {
	var stmt statement.SelectCoreStmt
	var err error

	// Parse "SELECT".
	if err := p.ParseTokens(scanner.SELECT); err != nil {
		return nil, err
	}

	stmt.Distinct, err = p.parseOptional(scanner.DISTINCT)
	if err != nil {
		return nil, err
	}

	// Parse path list or query.Wildcard
	stmt.ProjectionExprs, err = p.parseProjectedExprs()
	if err != nil {
		return nil, err
	}

	// Parse "FROM".
	stmt.TableName, err = p.parseFrom()
	if err != nil {
		return nil, err
	}

	// Parse condition: "WHERE expr".
	stmt.WhereExpr, err = p.parseCondition()
	if err != nil {
		return nil, err
	}

	// Parse group by: "GROUP BY expr"
	stmt.GroupByExpr, err = p.parseGroupBy()
	if err != nil {
		return nil, err
	}

	return &stmt, nil
}

// parseProjectedExprs parses the list of projected fields.
func (p *Parser) parseProjectedExprs() ([]expr.Expr, error) {
	// Parse first (required) result path.
	pe, err := p.parseProjectedExpr()
	if err != nil {
		return nil, err
	}
	pexprs := []expr.Expr{pe}

	// Parse remaining (optional) result fields.
	for {
		if tok, _, _ := p.ScanIgnoreWhitespace(); tok != scanner.COMMA {
			p.Unscan()
			return pexprs, nil
		}

		pe, err := p.parseProjectedExpr()
		if err != nil {
			return nil, err
		}

		pexprs = append(pexprs, pe)
	}
}

// parseProjectedExpr parses one projected expression.
func (p *Parser) parseProjectedExpr() (expr.Expr, error) {
	// Check if the * token exists.
	if tok, _, _ := p.ScanIgnoreWhitespace(); tok == scanner.MUL {
		return expr.Wildcard{}, nil
	}
	p.Unscan()

	pe, err := p.ParseExpr()
	if err != nil {
		return nil, err
	}

	ne := &expr.NamedExpr{Expr: pe}

	// Check if the AS token exists.
	if tok, _, _ := p.ScanIgnoreWhitespace(); tok == scanner.AS {
		ne.ExprName, err = p.parseIdent()
		if err != nil {
			return nil, err
		}

		return ne, nil
	}
	p.Unscan()

	ne.ExprName = pe.String()

	return ne, nil
}

func (p *Parser) parseFrom() (string, error) {
	if ok, err := p.parseOptional(scanner.FROM); !ok || err != nil {
		return "", err
	}

	// Parse table name
	ident, err := p.parseIdent()
	if err != nil {
		pErr := errors.Unwrap(err).(*ParseError)
		pErr.Expected = []string{"table_name"}
		return ident, pErr
	}

	return ident, nil
}

func (p *Parser) parseGroupBy() (expr.Expr, error) {
	ok, err := p.parseOptional(scanner.GROUP, scanner.BY)
	if err != nil || !ok {
		return nil, err
	}

	// parse expr
	e, err := p.ParseExpr()
	return e, err
}
