package querycodec

// QueryRequest is the L1-owned result of parsing one Salesforce-compatible
// query. Runtime never sees this product-language shape; the codec lowers it
// to the neutral query contract before invoking its Executor.
type QueryRequest struct {
	DateAnchor      *DateAnchor
	ObjectAPIName   string
	Filter          Predicate
	FilterExpr      string
	SortField       string
	SortDirection   string
	Limit           int
	Offset          int
	SkipTotalSize   bool
	ScanAll         bool
	OrderBy         []SortCriterion
	SelectFields    []string
	GroupBy         string
	HavingPredicate Predicate
	Having          string
	SubQueries      []SubQueryRequest
	SemiJoins       []SemiJoinClause
}

type SortCriterion struct {
	Field     string
	Direction string
}

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

type SemiJoinClause struct {
	OuterField string
	Negate     bool
	InnerQuery *QueryRequest
	Index      int
}

type PredicateKind string

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

type OperandKind string

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

type NumericLiteral string

// DateLiteralValue and DateTimeLiteralValue preserve the lexical type of
// unquoted SOQL temporal literals after parsing. A quoted value with the same
// text remains an ordinary string. DateTimeLiteralValue is canonical UTC
// RFC3339Nano so it can be lowered directly into the neutral Runtime query
// contract without guessing from string contents.
type DateLiteralValue string
type DateTimeLiteralValue string

// FormulaText preserves the existing in-process formula lowering while the
// cross-process adapter consumes the canonical RFC3339Nano value directly.
func (value DateTimeLiteralValue) FormulaText() string {
	return normalizeDateTimeLiteral(string(value))
}

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

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

type CorrelatedMembership struct {
	ID                 string
	OuterField         string
	Negate             bool
	InnerObjectAPIName string
	InnerSelectField   string
	InnerFilter        Predicate
	InnerLimit         int
	InnerScanAll       bool
}

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

func LiteralOperand(value any) 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 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 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 {
	return joinPredicate(PredicateAnd, predicates)
}

func Or(predicates ...Predicate) Predicate {
	return joinPredicate(PredicateOr, predicates)
}

func joinPredicate(kind PredicateKind, predicates []Predicate) Predicate {
	children := make([]Predicate, 0, len(predicates))
	for _, predicate := range predicates {
		if predicate.Kind == PredicateNone {
			continue
		}
		if predicate.Kind == kind {
			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: kind, Children: children}
	}
}
