package ast

import (
	"fmt"
	"strings"

	"github.com/neilotoole/sq/libsq/core/stringz"

	"github.com/antlr/antlr4/runtime/Go/antlr/v4"

	"github.com/neilotoole/sq/libsq/ast/internal/slq"
)

// VisitSelectorElement implements slq.SLQVisitor.
func (v *parseTreeVisitor) VisitSelectorElement(ctx *slq.SelectorElementContext) any {
	node, err := newSelectorNode(v.cur, ctx.Selector())
	if err != nil {
		return err
	}

	if aliasCtx := ctx.Alias(); aliasCtx != nil {
		if aliasCtx.ID() != nil {
			node.alias = aliasCtx.ID().GetText()
		}
		if aliasCtx.STRING() != nil {
			node.alias = stringz.StripDoubleQuote(aliasCtx.STRING().GetText())
		}
	}

	if err := v.cur.AddChild(node); err != nil {
		return err
	}

	// No need to descend to the children, because we've already dealt
	// with them in this function.
	return nil
}

// VisitSelector implements slq.SLQVisitor.
func (v *parseTreeVisitor) VisitSelector(ctx *slq.SelectorContext) any {
	node, err := newSelectorNode(v.cur, ctx)
	if err != nil {
		return err
	}

	return v.cur.AddChild(node)
}

const (
	msgNodeNoAddChild    = "%T cannot add children: failed to add %T"
	msgNodeNoAddChildren = "%T cannot add children: failed to add %d children"
)

func newSelectorNode(parent Node, ctx slq.ISelectorContext) (*SelectorNode, error) {
	selNode := &SelectorNode{}
	selNode.parent = parent
	selNode.ctx = ctx
	selNode.text = ctx.GetText()

	var err error
	names := ctx.AllNAME()
	switch len(names) {
	default:
		return nil, errorf("expected 1 or 2 name parts in selector (e.g. '.table.column') but got %d parts: %s",
			len(names), ctx.GetText())
	case 1:
		if selNode.name0, err = extractSelVal(names[0]); err != nil {
			return nil, err
		}
	case 2:
		if selNode.name0, err = extractSelVal(names[0]); err != nil {
			return nil, err
		}
		if selNode.name1, err = extractSelVal(names[1]); err != nil {
			return nil, err
		}
	}

	return selNode, nil
}

var (
	_ Node     = (*SelectorNode)(nil)
	_ Selector = (*SelectorNode)(nil)
)

// SelectorNode is a selector such as ".my_table" or ".my_col". The
// generic selector will typically be replaced with a more specific
// selector node such as TblSelectorNode or ColSelectorNode.
type SelectorNode struct {
	baseNode

	// alias is the (optional) alias part. For example, given ".first_name:given_name",
	// the alias value is "given_name". May be empty.
	alias string

	// name0 is the first name part.
	//  - .actor -> name0 = .actor
	//  - .first_name -> name0 = .first_name
	//  - .actor.first_name -> name0 = .actor
	name0 string

	// name1 is the optional second name part.
	// - .actor --> name1 = EMPTY
	// - .actor.first_name -> name1 = first_name
	name1 string
}

// selector implements the ast.selector marker interface.
func (s *SelectorNode) selector() {
}

// Strings returns a log/debug-friendly representation.
func (s *SelectorNode) String() string {
	return nodeString(s)
}

// SelValue returns the selector value.
// See extractSelValue.
func (s *SelectorNode) SelValue() (string, error) {
	return extractSelVal(s.ctx)
}

var (
	_ Node   = (*TblSelectorNode)(nil)
	_ Tabler = (*TblSelectorNode)(nil)
)

// TblSelectorNode is a selector for a table, such as ".my_table"
// or "@my_src.my_table".
type TblSelectorNode struct {
	SelectorNode

	handle  string
	tblName string
}

// newTblSelector creates a new TblSelectorNode from ctx.
func newTblSelector(selNode *SelectorNode) (*TblSelectorNode, error) { //nolint:unparam
	n := &TblSelectorNode{
		SelectorNode: *selNode,
		tblName:      selNode.name0,
	}

	return n, nil
}

// TblName returns the table name. This is the raw value without punctuation.
func (s *TblSelectorNode) TblName() string {
	return s.tblName
}

// Handle returns the handle, which may be empty.
func (s *TblSelectorNode) Handle() string {
	return s.handle
}

// Tabler implements the Tabler marker interface.
func (s *TblSelectorNode) tabler() {
	// no-op
}

// SelValue returns the table name.
// TODO: Can we get rid of this method SelValue?
func (s *TblSelectorNode) SelValue() (string, error) {
	return s.TblName(), nil
}

// String returns a log/debug-friendly representation.
func (s *TblSelectorNode) String() string {
	text := nodeString(s)
	selVal, err := s.SelValue()
	if err != nil {
		selVal = "error: " + err.Error()
	}
	text += fmt.Sprintf(" | table: {%s} | datasource: {%s}", selVal, s.Handle())
	return text
}

var (
	_ Node         = (*TblColSelectorNode)(nil)
	_ ResultColumn = (*TblColSelectorNode)(nil)
	_ Selector     = (*TblColSelectorNode)(nil)
)

// TblColSelectorNode models the TABLE.COLUMN selector, e.g. actor.first_name.
type TblColSelectorNode struct {
	*SelectorNode
	tblName string
	colName string
}

// IsColumn implements ResultColumn.
func (n *TblColSelectorNode) IsColumn() bool {
	return true
}

// Text implements ResultColumn.
func (n *TblColSelectorNode) Text() string {
	return n.ctx.GetText()
}

func newTblColSelectorNode(selNode *SelectorNode) (*TblColSelectorNode, error) {
	n := &TblColSelectorNode{
		SelectorNode: selNode,
		tblName:      selNode.name0,
		colName:      selNode.name1,
	}

	if n.tblName == "" {
		return nil, errorf("cannot create %T: table name is empty: %s", n, n.Text())
	}

	if n.colName == "" {
		return nil, errorf("cannot create %T: column name is empty: %s", n, n.Text())
	}

	return n, nil
}

// TblName returns the table name, e.g. actor.
func (n *TblColSelectorNode) TblName() string {
	return n.tblName
}

// ColName returns the column name, e.g. first_name.
func (n *TblColSelectorNode) ColName() string {
	return n.colName
}

// String returns a log/debug-friendly representation.
func (n *TblColSelectorNode) String() string {
	return fmt.Sprintf("%T: %s", n, n.ctx.GetText())
}

// Alias returns the column alias, which may be empty.
// For example, given the selector ".first_name:given_name",
// the alias is "given_name".
func (n *TblColSelectorNode) Alias() string {
	return n.alias
}

var (
	_ Node         = (*ColSelectorNode)(nil)
	_ ResultColumn = (*ColSelectorNode)(nil)
	_ Selector     = (*ColSelectorNode)(nil)
)

// ColSelectorNode models a column selector such as ".first_name".
type ColSelectorNode struct {
	*SelectorNode

	colName string
}

// newColSelectorNode returns a ColSelectorNode constructed from ctx.
func newColSelectorNode(selNode *SelectorNode) (*ColSelectorNode, error) { //nolint:unparam
	n := &ColSelectorNode{SelectorNode: selNode}
	n.colName = selNode.name0
	return n, nil
}

// Text implements ResultColumn.
func (n *ColSelectorNode) Text() string {
	return n.ctx.GetText()
}

// IsColumn always returns true.
func (n *ColSelectorNode) IsColumn() bool {
	return true
}

// ColName returns the column name. Note the name is not escaped/quoted,
// thus it could contain whitespace, etc.
func (n *ColSelectorNode) ColName() string {
	return n.colName
}

// Alias returns the column alias, which may be empty.
// For example, given the selector ".first_name:given_name",
// the alias is "given_name".
func (n *ColSelectorNode) Alias() string {
	return n.alias
}

// String returns a log/debug-friendly representation.
func (n *ColSelectorNode) String() string {
	str := nodeString(n)
	if n.alias != "" {
		str += ":" + n.alias
	}
	return str
}

var _ Node = (*CmprNode)(nil)

// CmprNode models a comparison, such as ".age == 42".
type CmprNode struct {
	baseNode
}

// String returns a log/debug-friendly representation.
func (c *CmprNode) String() string {
	return nodeString(c)
}

func newCmprNode(parent Node, ctx slq.ICmprContext) *CmprNode {
	leaf, _ := ctx.GetChild(0).(*antlr.TerminalNodeImpl) // FIXME: return an error
	cmpr := &CmprNode{}
	cmpr.ctx = leaf
	cmpr.text = leaf.GetText()
	cmpr.parent = parent
	return cmpr
}

// extractSelVal extracts the value of the selector. The function takes
// a selector node type as input, e.g. ast.SelectorNode.
// Example inputs:
//
//   - .actor --> actor
//   - .first_name --> first_name
//   - ."first name" --> first name
//
// The function will remove the leading period, and quotes around the name.
func extractSelVal(ctx antlr.ParseTree) (string, error) {
	if ctx == nil {
		return "", errorf("invalid selector: is nil")
	}
	original := ctx.GetText()
	if len(original) < 2 {
		return "", errorf("invalid selector expression: too short: %s", original)
	}

	if original[0] != '.' {
		return "", errorf("illegal selector expression: must start with period: %s", original)
	}

	// Trim the leading period, e.g. ".first_name" -> "first_name".
	wip := original[1:]

	// Remove quotes if applicable.
	if wip[0] == '"' {
		if wip[len(wip)-1] != '"' {
			return "", errorf("illegal selector expression: unmatched quotes on string: %s", original)
		}

		wip = strings.TrimPrefix(wip, `"`)
		wip = strings.TrimSuffix(wip, `"`)

		if len(wip) == 0 {
			return "", errorf("invalid selector expression: too short: %s", original)
		}
	}

	return wip, nil
}