88namespace SportClimbing \IfscCalendar \Domain \StartList ;
99
1010use Closure ;
11+ use SportClimbing \IfscCalendar \Domain \Athlete \IFSCAthlete ;
1112use SportClimbing \IfscCalendar \Domain \Athlete \IFSCAthleteException ;
1213use SportClimbing \IfscCalendar \Domain \Athlete \IFSCAthleteService ;
1314use SportClimbing \IfscCalendar \Domain \Ranking \IFSCAthleteRankingCalculator ;
1415
15- use SportClimbing \IfscCalendar \Domain \Round \ IFSCRoundCategory ;
16+ use SportClimbing \IfscCalendar \Domain \Athlete \ IFSCAthleteGender ;
1617
1718final readonly class IFSCStartListGenerator
1819{
19- private const int LIST_MAX_SIZE = 40 ;
20+ private const int PER_GENDER_MAX = 20 ;
2021
2122 public function __construct (
2223 private IFSCStartListProviderInterface $ startListProvider ,
@@ -37,25 +38,55 @@ public function buildStartList(int $eventId): IFSCStartListResult
3738 $ athlete = $ this ->athleteService ->fetchAthlete ($ starter ->athleteId );
3839 $ starter ->score = $ this ->rankingCalculator ->calculateScore ($ athlete );
3940 $ starter ->photoUrl = $ athlete ->photoUrl ;
40- $ starter ->instagram = $ athlete ->instagram ;
41-
42- $ starter ->category = match ($ athlete ->gender ) {
43- 'male ' => IFSCRoundCategory::MEN ,
44- 'female ' => IFSCRoundCategory::WOMEN ,
45- default => null ,
46- };
41+ $ starter ->instagram = $ this ->normalizeInstagram ($ athlete ->instagram );
42+ $ starter ->gender = $ this ->getGender ($ athlete );
4743
4844 $ startList [] = $ starter ;
4945 }
5046
5147 usort ($ startList , $ this ->sortByScore ());
5248
5349 return new IFSCStartListResult (
54- starters: array_slice ($ startList, 0 , self :: LIST_MAX_SIZE ),
50+ starters: $ this -> selectTopByGender ($ startList ),
5551 total: count ($ startList ),
5652 );
5753 }
5854
55+ /**
56+ * @param IFSCStarter[] $startList
57+ * @return IFSCStarter[]
58+ */
59+ private function selectTopByGender (array $ startList ): array
60+ {
61+ $ men = $ this ->filterByGender ($ startList , IFSCAthleteGender::MEN );
62+ $ women = $ this ->filterByGender ($ startList , IFSCAthleteGender::WOMEN );
63+
64+ $ selectedMen = $ this ->selectTopFromPool ($ men , array_slice ($ women , self ::PER_GENDER_MAX ));
65+ $ selectedWomen = $ this ->selectTopFromPool ($ women , array_slice ($ men , self ::PER_GENDER_MAX ));
66+
67+ $ result = array_merge ($ selectedMen , $ selectedWomen );
68+ usort ($ result , $ this ->sortByScore ());
69+
70+ return $ result ;
71+ }
72+
73+ /**
74+ * @param IFSCStarter[] $pool
75+ * @param IFSCStarter[] $fillPool
76+ * @return IFSCStarter[]
77+ */
78+ private function selectTopFromPool (array $ pool , array $ fillPool ): array
79+ {
80+ $ selected = array_slice ($ pool , 0 , self ::PER_GENDER_MAX );
81+ $ shortfall = self ::PER_GENDER_MAX - count ($ selected );
82+
83+ if ($ shortfall > 0 ) {
84+ $ selected = array_merge ($ selected , array_slice ($ fillPool , 0 , $ shortfall ));
85+ }
86+
87+ return $ selected ;
88+ }
89+
5990 private function sortByScore (): Closure
6091 {
6192 return static function (IFSCStarter $ athlete1 , IFSCStarter $ athlete2 ): int {
@@ -69,6 +100,39 @@ private function sortByScore(): Closure
69100 };
70101 }
71102
103+ /** @return IFSCStarter[] */
104+ public function filterByGender (array $ startList , IFSCAthleteGender $ gender ): array
105+ {
106+ return array_values (array_filter ($ startList , fn (IFSCStarter $ starter ): bool => $ starter ->gender === $ gender ));
107+ }
108+
109+ private function normalizeInstagram (?string $ instagram ): ?string
110+ {
111+ if ($ instagram === null || $ instagram === '' ) {
112+ return null ;
113+ }
114+
115+ if (str_contains ($ instagram , 'instagram.com/ ' )) {
116+ preg_match ('~instagram\.com/([^/?#]+)~ ' , $ instagram , $ matches );
117+ return $ matches [1 ] ?? null ;
118+ }
119+
120+ return ltrim ($ instagram , '@ ' );
121+ }
122+
123+ /**
124+ * @param IFSCAthlete $athlete
125+ * @return IFSCAthleteGender|null
126+ */
127+ private function getGender (IFSCAthlete $ athlete ): ?IFSCAthleteGender
128+ {
129+ return match ($ athlete ->gender ) {
130+ 'male ' => IFSCAthleteGender::MEN ,
131+ 'female ' => IFSCAthleteGender::WOMEN ,
132+ default => null ,
133+ };
134+ }
135+
72136 /**
73137 * @return IFSCStarter[]
74138 * @throws IFSCStartListException
0 commit comments