package queryspec

import (
	"fmt"
	"regexp"
	"strings"
)

var numericLiteralPattern = regexp.MustCompile(`^-?(0|[1-9][0-9]*)(?:\.[0-9]+)?$`)

// InternalLikeExpressionFunction is the reserved bridge used when a structured
// LIKE predicate must pass through the transitional residual-expression
// executor. It is generated by this package and is not an authored formula
// function or an external query-language surface.
const InternalLikeExpressionFunction = "__NCRM_QUERY_LIKE__"

// NumericLiteral is an exact base-10 predicate literal. It is distinct from a
// string so formula lowering emits it without quotes, while structured SQL
// criteria can still pass its underlying text to the database without a
// float64 round trip.
type NumericLiteral string

type PredicateKind string

const (
	PredicateNone                 PredicateKind = ""
	PredicateAnd                  PredicateKind = "And"
	PredicateOr                   PredicateKind = "Or"
	PredicateNot                  PredicateKind = "Not"
	PredicateExpression           PredicateKind = "Expression"
	PredicateComparison           PredicateKind = "Comparison"
	PredicateFormulaExpression    PredicateKind = "FormulaExpression"
	PredicateFieldComparison      PredicateKind = "FieldComparison"
	PredicateCorrelatedMembership PredicateKind = "CorrelatedMembership"
	PredicateUnsupported          PredicateKind = "Unsupported"
)

type OperandKind string

const (
	OperandNone     OperandKind = ""
	OperandField    OperandKind = "Field"
	OperandLiteral  OperandKind = "Literal"
	OperandRaw      OperandKind = "Raw"
	OperandList     OperandKind = "List"
	OperandFunction OperandKind = "Function"
)

type Operand struct {
	Kind     OperandKind
	Field    string
	Value    interface{}
	Raw      string
	Function string
	Args     []Operand
	Elements []Operand
}

type Predicate struct {
	Kind                 PredicateKind
	Expression           string
	Field                string
	Op                   string
	Value                interface{}
	Left                 Operand
	Right                Operand
	Operand              Operand
	Children             []Predicate
	CorrelatedMembership *CorrelatedMembership
	UnsupportedReason    string
}

// CorrelatedMembership is the neutral predicate carrier for a query membership
// test such as `outer IN (SELECT inner FROM object WHERE ...)`.
//
// It is intentionally only a predicate shape in this phase: executor ownership,
// read-target routing, RLS, and LIMIT semantics must be implemented by a later
// phase before this predicate is admitted to runtime lowering.
type CorrelatedMembership struct {
	ID                 string
	OuterField         string
	Negate             bool
	InnerObjectAPIName string
	InnerSelectField   string
	InnerFilter        Predicate
	InnerLimit         int
	InnerScanAll       bool
}

type PredicateLoweringMode string

const (
	// PredicateLoweringPreferCriteria lowers field comparisons into typed
	// criteria when the current executor can carry them safely, and returns a
	// residual expression string for the parts that still require the formula
	// expression compiler.
	PredicateLoweringPreferCriteria PredicateLoweringMode = "prefer_criteria"
	// PredicateLoweringExpressionOnly lowers the whole predicate into the
	// current expression string. Use only at executor surfaces that do not yet
	// have a typed criteria carrier, such as HAVING.
	PredicateLoweringExpressionOnly PredicateLoweringMode = "expression_only"
)

type PredicateLowering struct {
	Expression string
	Criteria   []QueryCriterion
}

func FormulaPredicate(expression string) Predicate {
	if expression == "" {
		return Predicate{}
	}
	return Predicate{Kind: PredicateFormulaExpression, Expression: expression}
}

func FieldOperand(field string) Operand {
	if field == "" {
		return Operand{}
	}
	return Operand{Kind: OperandField, Field: field}
}

func LiteralOperand(value interface{}) Operand {
	return Operand{Kind: OperandLiteral, Value: value}
}

func RawOperand(raw string) Operand {
	if raw == "" {
		return Operand{}
	}
	return Operand{Kind: OperandRaw, Raw: raw}
}

func ListOperand(elements ...Operand) Operand {
	return Operand{Kind: OperandList, Elements: append([]Operand(nil), elements...)}
}

func FunctionOperand(name string, args ...Operand) Operand {
	if name == "" {
		return Operand{}
	}
	return Operand{Kind: OperandFunction, Function: name, Args: append([]Operand(nil), args...)}
}

func FieldPredicate(field string, op string, value interface{}) Predicate {
	if field == "" {
		return Predicate{}
	}
	return Predicate{Kind: PredicateFieldComparison, Field: field, Op: op, Value: value}
}

func ComparisonPredicate(left Operand, op string, right Operand) Predicate {
	if left.Kind == OperandNone || op == "" {
		return Predicate{}
	}
	return Predicate{Kind: PredicateComparison, Left: left, Op: op, Right: right}
}

func CorrelatedMembershipPredicate(m CorrelatedMembership) Predicate {
	if m.OuterField == "" || m.InnerObjectAPIName == "" || m.InnerSelectField == "" {
		return Predicate{}
	}
	copied := m
	return Predicate{Kind: PredicateCorrelatedMembership, CorrelatedMembership: &copied}
}

func UnsupportedPredicate(reason string) Predicate {
	if reason == "" {
		reason = "unsupported predicate"
	}
	return Predicate{Kind: PredicateUnsupported, UnsupportedReason: reason}
}

func ExpressionPredicate(operand Operand) Predicate {
	if operand.Kind == OperandNone {
		return Predicate{}
	}
	return Predicate{Kind: PredicateExpression, Operand: operand}
}

func Not(predicate Predicate) Predicate {
	if predicate.Kind == PredicateNone {
		return Predicate{}
	}
	return Predicate{Kind: PredicateNot, Children: []Predicate{predicate}}
}

func And(predicates ...Predicate) Predicate {
	children := make([]Predicate, 0, len(predicates))
	for _, predicate := range predicates {
		if predicate.Kind == PredicateNone {
			continue
		}
		if predicate.Kind == PredicateAnd {
			children = append(children, predicate.Children...)
			continue
		}
		children = append(children, predicate)
	}
	switch len(children) {
	case 0:
		return Predicate{}
	case 1:
		return children[0]
	default:
		return Predicate{Kind: PredicateAnd, Children: children}
	}
}

func Or(predicates ...Predicate) Predicate {
	children := make([]Predicate, 0, len(predicates))
	for _, predicate := range predicates {
		if predicate.Kind == PredicateNone {
			continue
		}
		if predicate.Kind == PredicateOr {
			children = append(children, predicate.Children...)
			continue
		}
		children = append(children, predicate)
	}
	switch len(children) {
	case 0:
		return Predicate{}
	case 1:
		return children[0]
	default:
		return Predicate{Kind: PredicateOr, Children: children}
	}
}

type QueryCriterion struct {
	Field string
	Op    string
	Val   interface{}
}

func CriteriaPredicate(criteria []QueryCriterion) Predicate {
	predicates := make([]Predicate, 0, len(criteria))
	for _, criterion := range criteria {
		predicates = append(predicates, FieldPredicate(criterion.Field, criterion.Op, criterion.Val))
	}
	return And(predicates...)
}

func (p Predicate) FormulaExpression() string {
	return formulaExpression(p)
}

// LowerPredicate is the single predicate lowering door. The predicate tree, not
// external SOQL/SOSL text, is the engine-facing contract; expression strings are
// derived here only as a transitional executor shape until the SQL emitter
// consumes the tree directly.
func LowerPredicate(p Predicate, mode PredicateLoweringMode) (PredicateLowering, error) {
	if mode == PredicateLoweringExpressionOnly {
		expression, err := renderPredicateExpression(p, mode)
		return PredicateLowering{Expression: expression}, err
	}

	switch p.Kind {
	case PredicateNone:
		return PredicateLowering{}, nil
	case PredicateFieldComparison:
		if criterion, ok := fieldComparisonCriterion(p.Field, p.Op, p.Value); ok {
			return PredicateLowering{Criteria: []QueryCriterion{criterion}}, nil
		}
	case PredicateComparison:
		if criterion, ok := comparisonCriterion(p); ok {
			return PredicateLowering{Criteria: []QueryCriterion{criterion}}, nil
		}
	case PredicateAnd:
		var lowering PredicateLowering
		expressions := make([]string, 0, len(p.Children))
		for _, child := range p.Children {
			childLowering, err := LowerPredicate(child, PredicateLoweringPreferCriteria)
			if err != nil {
				return PredicateLowering{}, err
			}
			if childLowering.Expression != "" {
				expressions = append(expressions, "("+childLowering.Expression+")")
			}
			lowering.Criteria = append(lowering.Criteria, childLowering.Criteria...)
		}
		lowering.Expression = strings.Join(expressions, " AND ")
		return lowering, nil
	}

	expression, err := renderPredicateExpression(p, PredicateLoweringPreferCriteria)
	if err != nil {
		return PredicateLowering{}, err
	}
	return PredicateLowering{Expression: expression}, nil
}

func renderPredicateExpression(p Predicate, mode PredicateLoweringMode) (string, error) {
	switch p.Kind {
	case PredicateNone:
		return "", nil
	case PredicateFormulaExpression:
		return p.Expression, nil
	case PredicateFieldComparison:
		literal, err := renderOperand(LiteralOperand(p.Value))
		if err != nil {
			return "", err
		}
		return renderComparisonExpression(p.Field, p.Op, literal, mode), nil
	case PredicateComparison:
		left, err := renderOperand(p.Left)
		if err != nil {
			return "", err
		}
		right, err := renderOperand(p.Right)
		if err != nil {
			return "", err
		}
		return renderComparisonExpression(left, p.Op, right, mode), nil
	case PredicateExpression:
		return renderOperand(p.Operand)
	case PredicateNot:
		if len(p.Children) == 0 {
			return "", nil
		}
		expr, err := renderPredicateExpression(p.Children[0], mode)
		if err != nil {
			return "", err
		}
		if expr == "" {
			return "", nil
		}
		return "not (" + expr + ")", nil
	case PredicateAnd:
		parts := make([]string, 0, len(p.Children))
		for _, child := range p.Children {
			expr, err := renderPredicateExpression(child, mode)
			if err != nil {
				return "", err
			}
			if expr != "" {
				parts = append(parts, "("+expr+")")
			}
		}
		return strings.Join(parts, " AND "), nil
	case PredicateOr:
		parts := make([]string, 0, len(p.Children))
		for _, child := range p.Children {
			expr, err := renderPredicateExpression(child, mode)
			if err != nil {
				return "", err
			}
			if expr != "" {
				parts = append(parts, "("+expr+")")
			}
		}
		return strings.Join(parts, " OR "), nil
	case PredicateUnsupported:
		return "", fmt.Errorf("unsupported search predicate: %s", p.UnsupportedReason)
	default:
		return "", fmt.Errorf("unsupported search predicate kind %q", p.Kind)
	}
}

func renderComparisonExpression(left string, operator string, right string, mode PredicateLoweringMode) string {
	if mode == PredicateLoweringPreferCriteria {
		switch strings.ToUpper(strings.TrimSpace(operator)) {
		case "LIKE":
			return fmt.Sprintf("%s(%s, %s)", InternalLikeExpressionFunction, left, right)
		case "NOT LIKE":
			return fmt.Sprintf("not (%s(%s, %s))", InternalLikeExpressionFunction, left, right)
		}
	}
	return fmt.Sprintf("%s %s %s", left, operator, right)
}

func formulaExpression(p Predicate) string {
	switch p.Kind {
	case PredicateFormulaExpression:
		return p.Expression
	case PredicateAnd:
		parts := make([]string, 0, len(p.Children))
		for _, child := range p.Children {
			if expr := formulaExpression(child); expr != "" {
				parts = append(parts, "("+expr+")")
			}
		}
		if len(parts) == 0 {
			return ""
		}
		expr := parts[0]
		for _, part := range parts[1:] {
			expr += " and " + part
		}
		return expr
	default:
		return ""
	}
}

func fieldComparisonCriterion(field string, op string, value interface{}) (QueryCriterion, bool) {
	op, ok := criterionOperator(op)
	if !ok || field == "" {
		return QueryCriterion{}, false
	}
	if op == "IN" || op == "NOT IN" {
		values, ok := criterionListValue(value)
		if !ok {
			return QueryCriterion{}, false
		}
		value = values
	} else if value == nil {
		return QueryCriterion{}, false
	}
	return QueryCriterion{Field: field, Op: op, Val: value}, true
}

func comparisonCriterion(p Predicate) (QueryCriterion, bool) {
	if p.Left.Kind != OperandField || p.Left.Field == "" {
		return QueryCriterion{}, false
	}
	op, ok := criterionOperator(p.Op)
	if !ok {
		return QueryCriterion{}, false
	}
	value, ok := criterionOperandValue(p.Right)
	if !ok {
		return QueryCriterion{}, false
	}
	if op == "IN" || op == "NOT IN" {
		values, ok := criterionListValue(value)
		if !ok {
			return QueryCriterion{}, false
		}
		value = values
	} else if value == nil {
		return QueryCriterion{}, false
	}
	return QueryCriterion{Field: p.Left.Field, Op: op, Val: value}, true
}

func criterionOperator(op string) (string, bool) {
	switch strings.ToUpper(strings.TrimSpace(op)) {
	case "=", "==":
		return "=", true
	case "!=", "<>":
		return "!=", true
	case ">", "<", ">=", "<=":
		return strings.ToUpper(strings.TrimSpace(op)), true
	case "LIKE", "NOT LIKE":
		return strings.ToUpper(strings.TrimSpace(op)), true
	case "IN":
		return "IN", true
	case "NOT IN":
		return "NOT IN", true
	default:
		return "", false
	}
}

func criterionOperandValue(operand Operand) (interface{}, bool) {
	switch operand.Kind {
	case OperandLiteral:
		if exact, ok := operand.Value.(NumericLiteral); ok {
			return string(exact), true
		}
		return operand.Value, true
	case OperandRaw:
		if strings.EqualFold(strings.TrimSpace(operand.Raw), "null") ||
			strings.EqualFold(strings.TrimSpace(operand.Raw), "nil") {
			return nil, true
		}
		return operand.Raw, true
	case OperandList:
		values := make([]interface{}, 0, len(operand.Elements))
		for _, element := range operand.Elements {
			value, ok := criterionOperandValue(element)
			if !ok {
				return nil, false
			}
			values = append(values, value)
		}
		return values, true
	default:
		return nil, false
	}
}

func criterionListValue(value interface{}) ([]interface{}, bool) {
	switch v := value.(type) {
	case []interface{}:
		return append([]interface{}(nil), v...), true
	case []string:
		out := make([]interface{}, len(v))
		for i := range v {
			out[i] = v[i]
		}
		return out, true
	default:
		return nil, false
	}
}

func renderOperand(operand Operand) (string, error) {
	switch operand.Kind {
	case OperandNone:
		return "", nil
	case OperandField:
		if operand.Field == "" {
			return "", fmt.Errorf("field operand is empty")
		}
		return operand.Field, nil
	case OperandLiteral:
		return filterExpressionLiteral(operand.Value)
	case OperandRaw:
		if operand.Raw == "" {
			return "", fmt.Errorf("raw operand is empty")
		}
		return operand.Raw, nil
	case OperandList:
		parts := make([]string, 0, len(operand.Elements))
		for _, element := range operand.Elements {
			rendered, err := renderOperand(element)
			if err != nil {
				return "", err
			}
			parts = append(parts, rendered)
		}
		return "[" + strings.Join(parts, ",") + "]", nil
	case OperandFunction:
		if operand.Function == "" {
			return "", fmt.Errorf("function operand is empty")
		}
		parts := make([]string, 0, len(operand.Args))
		for _, arg := range operand.Args {
			rendered, err := renderOperand(arg)
			if err != nil {
				return "", err
			}
			parts = append(parts, rendered)
		}
		return operand.Function + "(" + strings.Join(parts, ", ") + ")", nil
	default:
		return "", fmt.Errorf("unsupported operand kind %q", operand.Kind)
	}
}

func filterExpressionLiteral(value interface{}) (string, error) {
	switch v := value.(type) {
	case nil:
		return "nil", nil
	case string:
		escaped := strings.ReplaceAll(v, "\\", "\\\\")
		escaped = strings.ReplaceAll(escaped, "'", "\\'")
		return "'" + escaped + "'", nil
	case bool:
		if v {
			return "true", nil
		}
		return "false", nil
	case []interface{}:
		return filterExpressionLiteralList(v)
	case []string:
		values := make([]interface{}, len(v))
		for i := range v {
			values[i] = v[i]
		}
		return filterExpressionLiteralList(values)
	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
		return fmt.Sprint(v), nil
	case NumericLiteral:
		if !numericLiteralPattern.MatchString(string(v)) {
			return "", fmt.Errorf("invalid exact numeric literal %q", v)
		}
		return string(v), nil
	default:
		return "", fmt.Errorf("unsupported search predicate value %T", value)
	}
}

func filterExpressionLiteralList(values []interface{}) (string, error) {
	parts := make([]string, 0, len(values))
	for _, value := range values {
		rendered, err := filterExpressionLiteral(value)
		if err != nil {
			return "", err
		}
		parts = append(parts, rendered)
	}
	return "[" + strings.Join(parts, ",") + "]", nil
}

func (p Predicate) Criteria() []QueryCriterion {
	lowering, err := LowerPredicate(p, PredicateLoweringPreferCriteria)
	if err != nil || lowering.Expression != "" {
		return nil
	}
	return lowering.Criteria
}

type SortCriterion struct {
	Field     string
	Direction string
}

type SubQuerySpec struct {
	RelationshipName string
	SelectFields     []string
	Filter           Predicate
	OrderBy          []SortCriterion
	Limit            int
}

// ReferenceHydrationMode controls whether selecting a root reference field
// also implies loading its display value. The zero value preserves the public
// compatibility behavior. ExplicitOnly is for compiler-bounded internal reads
// whose exact relationship consumers are already present in SelectFields.
type ReferenceHydrationMode uint8

const (
	ReferenceHydrationDefault ReferenceHydrationMode = iota
	ReferenceHydrationExplicitOnly
)

func (m ReferenceHydrationMode) Valid() bool {
	return m == ReferenceHydrationDefault || m == ReferenceHydrationExplicitOnly
}

// QuerySpec is the neutral engine-facing shape. External query grammars lower
// WHERE/HAVING clauses to Predicate trees before crossing into L0. Formula
// predicates remain available for real formula filters and compatibility callers
// that have not yet grown structured IR.
type QuerySpec struct {
	ObjectAPIName string
	SelectFields  []string
	Filter        Predicate
	SortField     string
	SortDirection string
	Limit         int
	Offset        int
	SkipTotalSize bool
	ScanAll       bool
	OrderBy       []SortCriterion
	GroupBy       string
	Having        Predicate
	SubQueries    []SubQuerySpec
	// ReferenceHydration applies to this QuerySpec's root projection. Child
	// subqueries retain their own default compatibility behavior.
	ReferenceHydration ReferenceHydrationMode
}

// CountSpec is the neutral engine-facing shape for an exact filtered count.
// It intentionally excludes row projection, pagination, sorting, grouping and
// subqueries so count-only callers cannot accidentally enter the full row-read
// and virtual-field hydration pipeline.
type CountSpec struct {
	ObjectAPIName string
	Filter        Predicate
	ScanAll       bool
}

type SearchSpec struct {
	ObjectAPIName string
	Term          string
	SearchFields  []string
	ReturnFields  []string
	Filter        Predicate
	SortField     string
	SortDirection string
	Limit         int
	Offset        int
	// Prefix requests anchored prefix matching instead of the default substring
	// search. ForceBaseTable bypasses formula-view reads for index-eligible
	// physical-column prefix lookups. Both are neutral engine execution options;
	// endpoint-specific overfetch and wire envelopes stay in the dialect layer.
	Prefix         bool
	ForceBaseTable bool
}

type GlobalSearchSpec struct {
	Term string
}
