join('users as u', 'u.id', '=', 'uf.follower_id') ->leftJoin('user_profiles as up', 'up.user_id', '=', 'u.id') ->leftJoin('user_statistics as us', 'us.user_id', '=', 'u.id') ->leftJoin('user_followers as mutual', function ($join) use ($user): void { $join->on('mutual.user_id', '=', 'uf.follower_id') ->where('mutual.follower_id', '=', $user->id); }) ->where('uf.user_id', $user->id) ->whereNull('u.deleted_at') ->when($search !== '', function ($query) use ($search): void { $query->where(function ($inner) use ($search): void { $inner->where('u.username', 'like', '%' . $search . '%') ->orWhere('u.name', 'like', '%' . $search . '%'); }); }) ->when($relationship === 'following-back', fn ($query) => $query->whereNotNull('mutual.created_at')) ->when($relationship === 'not-followed', fn ($query) => $query->whereNull('mutual.created_at')); $summaryBaseQuery = clone $baseQuery; $followers = $baseQuery ->when($sort === 'recent', fn ($query) => $query->orderByDesc('uf.created_at')) ->when($sort === 'oldest', fn ($query) => $query->orderBy('uf.created_at')) ->when($sort === 'name', fn ($query) => $query->orderByRaw('COALESCE(u.username, u.name) asc')) ->when($sort === 'uploads', fn ($query) => $query->orderByDesc('us.uploads_count')->orderByRaw('COALESCE(u.username, u.name) asc')) ->when($sort === 'followers', fn ($query) => $query->orderByDesc('us.followers_count')->orderByRaw('COALESCE(u.username, u.name) asc')) ->select([ 'u.id', 'u.username', 'u.name', 'up.avatar_hash', 'us.uploads_count', 'us.followers_count', 'uf.created_at as followed_at', 'mutual.created_at as followed_back_at', ]) ->paginate($perPage, ['*'], 'page', $page) ->withQueryString(); return [ 'items' => collect($followers->items())->map(fn ($row): array => [ 'id' => (int) $row->id, 'name' => $row->name ?: '@' . $row->username, 'username' => $row->username, 'avatar_url' => AvatarUrl::forUser((int) $row->id, $row->avatar_hash, 64), 'profile_url' => '/@' . strtolower((string) ($row->username ?? $row->id)), 'uploads_count' => (int) ($row->uploads_count ?? 0), 'followers_count' => (int) ($row->followers_count ?? 0), 'is_following_back' => $row->followed_back_at !== null, 'followed_back_at' => $row->followed_back_at, 'followed_at' => $row->followed_at, ])->values()->all(), 'meta' => [ 'current_page' => $followers->currentPage(), 'last_page' => $followers->lastPage(), 'per_page' => $followers->perPage(), 'total' => $followers->total(), ], 'filters' => [ 'q' => $search, 'sort' => $sort, 'relationship' => $relationship, ], 'summary' => [ 'total_followers' => (clone $summaryBaseQuery)->count(), 'following_back' => (clone $summaryBaseQuery)->whereNotNull('mutual.created_at')->count(), 'not_followed' => (clone $summaryBaseQuery)->whereNull('mutual.created_at')->count(), ], 'sort_options' => [ ['value' => 'recent', 'label' => 'Most recent'], ['value' => 'oldest', 'label' => 'Oldest first'], ['value' => 'name', 'label' => 'Name A-Z'], ['value' => 'uploads', 'label' => 'Most uploads'], ['value' => 'followers', 'label' => 'Most followers'], ], 'relationship_options' => [ ['value' => 'all', 'label' => 'All followers'], ['value' => 'following-back', 'label' => 'Following back'], ['value' => 'not-followed', 'label' => 'Not followed yet'], ], ]; } }