using JSMR.Application.Circles.Queries.Search; using JSMR.Infrastructure.Common.Queries; using Microsoft.EntityFrameworkCore; using System.Linq.Expressions; namespace JSMR.Infrastructure.Data.Repositories.Circles; public class CircleSearchProvider(AppDbContext context) : SearchProvider, ICircleSearchProvider { protected override IQueryable GetBaseQuery() { // Precompute LatestProductId per circle (by productId length, then value) var latestPerCircle = from vw in context.VoiceWorks.AsNoTracking() group vw by vw.CircleId into g let latest = g .OrderByDescending(x => x.ProductId.Length) .ThenByDescending(x => x.ProductId) .Select(x => x.ProductId) .FirstOrDefault() select new { CircleId = g.Key, LatestProductId = latest }; // Aggregates per circle var aggregates = from vw in context.VoiceWorks.AsNoTracking() group vw by vw.CircleId into g select new { CircleId = g.Key, Downloads = g.Sum(x => x.Downloads ?? 0), Releases = g.Count(x => x.SalesDate != null), Pending = g.Count(x => x.ExpectedDate != null), FirstReleaseDate = g.Min(x => x.SalesDate), LatestReleaseDate = g.Max(x => x.SalesDate) }; // Join circles with aggregates and latest product id var baseQuery = from c in context.Circles.AsNoTracking() join agg in aggregates on c.CircleId equals agg.CircleId into aggs from a in aggs.DefaultIfEmpty() join lp in latestPerCircle on c.CircleId equals lp.CircleId into lps from l in lps.DefaultIfEmpty() select new CircleSearchItem { CircleId = c.CircleId, Name = c.Name, MakerId = c.MakerId, Favorite = c.Favorite, Blacklisted = c.Blacklisted, Spam = c.Spam, Downloads = a != null ? a.Downloads : 0, Releases = a != null ? a.Releases : 0, Pending = a != null ? a.Pending : 0, FirstReleaseDate = a != null ? a.FirstReleaseDate : null, LatestReleaseDate = a != null ? a.LatestReleaseDate : null, LatestProductId = l != null ? l.LatestProductId : null, // these two get filled in during Select stage (below) LatestVoiceWorkHasImage = null, LatestVoiceWorkSalesDate = null }; return baseQuery; } protected override IQueryable ApplyFilters(IQueryable query, CircleSearchCriteria criteria) { if (!string.IsNullOrWhiteSpace(criteria.Name)) { var term = $"%{criteria.Name.Trim()}%"; query = query.Where(x => EF.Functions.Like(x.Name, term) || EF.Functions.Like(x.MakerId, term)); } //if (criteria.Status is CircleStatus.NotBlacklisted) // query = query.Where(x => !x.Blacklisted); //else if (criteria.Status is CircleStatus.Favorited) // query = query.Where(x => x.Favorite); //else if (criteria.Status is CircleStatus.Blacklisted) // query = query.Where(x => x.Blacklisted); //else if (criteria.Status is CircleStatus.Spam) // query = query.Where(x => x.Spam); return query; } protected override Expression> GetSortExpression(CircleSortField field) => field switch { //CircleSortField.MakerId => x => x.MakerId, //CircleSortField.Downloads => x => x.Downloads, //CircleSortField.Releases => x => x.Releases, //CircleSortField.Pending => x => x.Pending, //CircleSortField.FirstReleaseDate => x => x.FirstReleaseDate ?? DateTime.MinValue, //CircleSortField.LatestReleaseDate => x => x.LatestReleaseDate ?? DateTime.MinValue, CircleSortField.Favorite => x => x.Favorite, CircleSortField.Blacklisted => x => x.Blacklisted, CircleSortField.Spam => x => x.Spam, _ => x => x.Name }; protected override IOrderedQueryable GetDefaultSortExpression(IQueryable query) => query.OrderBy(x => x.Name).ThenBy(x => x.CircleId); protected override IOrderedQueryable GetSelectQuery(IOrderedQueryable query) { // Join to VoiceWorks by LatestProductId to fill HasImage / SalesDate var selected = from item in query join vw in context.VoiceWorks.AsNoTracking() on item.LatestProductId equals vw.ProductId into vws from latest in vws.DefaultIfEmpty() select new CircleSearchItem { CircleId = item.CircleId, Name = item.Name, MakerId = item.MakerId, Favorite = item.Favorite, Blacklisted = item.Blacklisted, Spam = item.Spam, Downloads = item.Downloads, Releases = item.Releases, Pending = item.Pending, FirstReleaseDate = item.FirstReleaseDate, LatestReleaseDate = item.LatestReleaseDate, LatestProductId = item.LatestProductId, LatestVoiceWorkHasImage = latest != null ? latest.HasImage : (bool?)null, LatestVoiceWorkSalesDate = latest != null ? latest.SalesDate : null }; // Preserve existing ordering; add stable tiebreaker return selected.OrderBy(x => 0).ThenBy(x => x.Name).ThenBy(x => x.CircleId); // NOTE: If your base class re-applies ordering after Select, you can just: // return selected.OrderBy(x => x.Name).ThenBy(x => x.CircleId); } }