using Baya.Application.Contracts.Persistence; using Baya.Application.Models.Geography; using Baya.Domain.Entities.Geography; using Microsoft.EntityFrameworkCore; namespace Baya.Infrastructure.Persistence.Repositories; internal sealed class GeoRepository : IGeoRepository { private readonly ApplicationDbContext _db; public GeoRepository(ApplicationDbContext db) => _db = db; public async Task> ListActiveProvincesAsync(CancellationToken cancellationToken) => await _db.Set() .AsNoTracking() .Where(p => p.IsActive) .OrderBy(p => p.SortOrder) .Select(p => new ProvinceDto(p.Id, p.NameFa, p.NameEn, p.SortOrder)) .ToListAsync(cancellationToken); public async Task> ListActiveCitiesAsync(long provinceId, CancellationToken cancellationToken) => await _db.Set() .AsNoTracking() .Where(c => c.ProvinceId == provinceId && c.IsActive && c.Province.IsActive) .OrderBy(c => c.SortOrder) .Select(c => new CityDto(c.Id, c.ProvinceId, c.NameFa, c.NameEn, c.SortOrder)) .ToListAsync(cancellationToken); public async Task> ListActiveDistrictsAsync(long cityId, CancellationToken cancellationToken) => await _db.Set() .AsNoTracking() .Where(d => d.CityId == cityId && d.IsActive && d.City.IsActive && d.City.Province.IsActive) .OrderBy(d => d.SortOrder) .Select(d => new DistrictDto(d.Id, d.CityId, d.NameFa, d.NameEn, d.SortOrder)) .ToListAsync(cancellationToken); public async Task> GetActiveTreeAsync(CancellationToken cancellationToken) { // Three flat ordered reads assembled in memory — avoids a two-level nested collection projection // (which SQLite can't translate) and keeps the payload cheap for a cached, near-static tree. var provinces = await _db.Set() .AsNoTracking() .Where(p => p.IsActive) .OrderBy(p => p.SortOrder) .Select(p => new { p.Id, p.NameFa, p.NameEn, p.SortOrder }) .ToListAsync(cancellationToken); var cities = await _db.Set() .AsNoTracking() .Where(c => c.IsActive && c.Province.IsActive) .OrderBy(c => c.SortOrder) .Select(c => new { c.Id, c.ProvinceId, c.NameFa, c.NameEn, c.SortOrder }) .ToListAsync(cancellationToken); var districts = await _db.Set() .AsNoTracking() .Where(d => d.IsActive && d.City.IsActive && d.City.Province.IsActive) .OrderBy(d => d.SortOrder) .Select(d => new DistrictDto(d.Id, d.CityId, d.NameFa, d.NameEn, d.SortOrder)) .ToListAsync(cancellationToken); var districtsByCity = districts .GroupBy(d => d.CityId) .ToDictionary(g => g.Key, g => (IReadOnlyList)g.ToList()); var citiesByProvince = cities .GroupBy(c => c.ProvinceId) .ToDictionary( g => g.Key, g => (IReadOnlyList)g .Select(c => new CityTreeDto( c.Id, c.NameFa, c.NameEn, c.SortOrder, districtsByCity.TryGetValue(c.Id, out var ds) ? ds : [])) .ToList()); return provinces .Select(p => new ProvinceTreeDto( p.Id, p.NameFa, p.NameEn, p.SortOrder, citiesByProvince.TryGetValue(p.Id, out var cs) ? cs : [])) .ToList(); } public Task GetProvinceAsync(long id, CancellationToken cancellationToken) => _db.Set().FirstOrDefaultAsync(p => p.Id == id, cancellationToken); public Task GetCityAsync(long id, CancellationToken cancellationToken) => _db.Set().FirstOrDefaultAsync(c => c.Id == id, cancellationToken); public Task GetDistrictAsync(long id, CancellationToken cancellationToken) => _db.Set().FirstOrDefaultAsync(d => d.Id == id, cancellationToken); public async Task AddProvinceAsync(Province province, CancellationToken cancellationToken) => await _db.Set().AddAsync(province, cancellationToken); public async Task AddCityAsync(City city, CancellationToken cancellationToken) => await _db.Set().AddAsync(city, cancellationToken); public async Task AddDistrictAsync(District district, CancellationToken cancellationToken) => await _db.Set().AddAsync(district, cancellationToken); public Task ProvinceExistsAsync(long provinceId, CancellationToken cancellationToken) => _db.Set().AsNoTracking().AnyAsync(p => p.Id == provinceId, cancellationToken); public Task IsCityActiveAsync(long cityId, CancellationToken cancellationToken) => _db.Set().AsNoTracking() .AnyAsync(c => c.Id == cityId && c.IsActive && c.Province.IsActive, cancellationToken); public Task IsDistrictInActiveCityAsync(long districtId, long cityId, CancellationToken cancellationToken) => _db.Set().AsNoTracking() .AnyAsync( d => d.Id == districtId && d.CityId == cityId && d.IsActive && d.City.IsActive && d.City.Province.IsActive, cancellationToken); }