package models

import "github.com/nexuscrm/shared/pkg/queryspec"

// QuerySpecFromQueryRequest projects a parsed-SOQL QueryRequest onto the neutral
// engine-facing queryspec.QuerySpec. It is the single source of truth for that
// mapping: every read door (the gin-free dialect-sf-crm query/queryMore/semi-join
// handlers and the build-tagged backend rest query + tooling-query handlers) calls
// this one function, so the SOQL→spec contract (notably the multi-column ORDER BY
// fact) cannot diverge between read paths.
//
// SortField/SortDirection carry only the first ORDER BY column (the SOQL parser
// populates the full ordered list on OrderBy while keeping SortField for MRU
// routing and read-target view selection). Both are forwarded: the engine sorts by
// the complete OrderBy list, and SortField stays available for the consumers that
// still read it.
func QuerySpecFromQueryRequest(req *QueryRequest) queryspec.QuerySpec {
	if req == nil {
		return queryspec.QuerySpec{}
	}
	spec := queryspec.QuerySpec{
		ObjectAPIName: req.ObjectAPIName,
		SelectFields:  append([]string(nil), req.SelectFields...),
		Filter:        queryRequestFilter(req),
		SortField:     req.SortField,
		SortDirection: req.SortDirection,
		Limit:         req.Limit,
		Offset:        req.Offset,
		SkipTotalSize: req.SkipTotalSize,
		ScanAll:       req.ScanAll,
		GroupBy:       req.GroupBy,
		Having:        queryRequestHaving(req),
	}
	// Top-level multi-column ORDER BY: forward the full ordered term list so the
	// engine sorts by the complete ORDER BY. Mapping SortField alone (as the read
	// doors previously did) silently dropped every term after the first, so
	// `ORDER BY A, B` ordered by A only.
	spec.OrderBy = SortCriteriaToQuerySpec(req.OrderBy)
	if len(req.SubQueries) > 0 {
		spec.SubQueries = make([]queryspec.SubQuerySpec, len(req.SubQueries))
		for i, sq := range req.SubQueries {
			spec.SubQueries[i] = queryspec.SubQuerySpec{
				RelationshipName: sq.RelationshipName,
				SelectFields:     append([]string(nil), sq.SelectFields...),
				Filter:           queryRequestSubQueryFilter(sq),
				Limit:            sq.Limit,
				OrderBy:          SortCriteriaToQuerySpec(sq.OrderBy),
			}
		}
	}
	return spec
}

func queryRequestFilter(req *QueryRequest) queryspec.Predicate {
	if req.FilterExpr != "" {
		return queryspec.FormulaPredicate(req.FilterExpr)
	}
	if req.Filter.Kind != queryspec.PredicateNone {
		return req.Filter
	}
	return queryspec.Predicate{}
}

func queryRequestHaving(req *QueryRequest) queryspec.Predicate {
	if req.Having != "" {
		return queryspec.FormulaPredicate(req.Having)
	}
	if req.HavingPredicate.Kind != queryspec.PredicateNone {
		return req.HavingPredicate
	}
	return queryspec.Predicate{}
}

func queryRequestSubQueryFilter(sq SubQueryRequest) queryspec.Predicate {
	if sq.FilterExpr != "" {
		return queryspec.FormulaPredicate(sq.FilterExpr)
	}
	if sq.Filter.Kind != queryspec.PredicateNone {
		return sq.Filter
	}
	return queryspec.Predicate{}
}

// SortCriteriaToQuerySpec converts a parsed-SOQL ORDER BY term list into the
// neutral queryspec form. It is the single owner of that conversion so every
// SOQL→spec mapper (the shared QuerySpecFromQueryRequest above and the Bulk API
// query mapper, which keeps its own slim projection + paging overrides) preserves
// multi-column ORDER BY identically. Returns nil for an empty list.
func SortCriteriaToQuerySpec(in []SortCriterion) []queryspec.SortCriterion {
	if len(in) == 0 {
		return nil
	}
	out := make([]queryspec.SortCriterion, len(in))
	for i, o := range in {
		out[i] = queryspec.SortCriterion{Field: o.Field, Direction: o.Direction}
	}
	return out
}
