package querycodec

// ParseOpts controls optional parser behaviors that produce request state
// the caller MUST handle (otherwise the request is unsafe to dispatch).
//
// Background: parsing `WHERE field IN (SELECT ...)` populates
// QueryRequest.SemiJoins with parsed inner queries and emits a sentinel
// placeholder predicate into the outer filter. The placeholder is meaningless to
// QueryService — every caller that dispatches the request MUST first lower it to
// a neutral correlated-membership predicate, invoke ResolveSemiJoins for a
// legacy compatibility surface, or reject the request. Forgetting to do so
// silently returns wrong rows.
//
// To make that obligation impossible to forget at any new caller site,
// ParseSOQL fail-closes by default: if the SOQL contains a semi-join,
// parsing returns a validation error. Callers that genuinely handle the parsed
// SemiJoins shape MUST opt in via ParseOpts{AllowSemiJoin: true}.
type ParseOpts struct {
	// AllowSemiJoin permits `IN (SELECT ...)` and `NOT IN (SELECT ...)`
	// in the WHERE clause. Set true ONLY when the call site converts or
	// resolves QueryRequest.SemiJoins before the request is dispatched.
	AllowSemiJoin bool
}

// ParseSOQL converts a SOQL string into a standard QueryRequest. Strict by
// default: semi-join syntax is rejected at parse time. Use ParseSOQLWith
// to opt into semi-join support.
func ParseSOQL(query string) (*QueryRequest, error) {
	return ParseSOQLWith(query, ParseOpts{})
}

// ParseSOQLWith parses SOQL with caller-controlled feature opt-ins. Use
// ParseOpts{AllowSemiJoin: true} only at sites that wire a post-parse SemiJoins
// handling step.
func ParseSOQLWith(query string, opts ParseOpts) (*QueryRequest, error) {
	l := NewLexer(query)
	p := NewParser(l)
	req, err := p.Parse()
	if err != nil {
		return nil, err
	}
	if !opts.AllowSemiJoin && len(req.SemiJoins) > 0 {
		return nil, newValidationError(
			"SOQL",
			"semi-join subqueries (IN (SELECT ...)) are not supported in this query context",
		)
	}
	return req, nil
}
