<?php
namespace App\Services;use App\Models\SerpCheck;use Illuminate\Support\Collection;
final class WinnersReportService{public function __construct(private VisibilityReportService $visibility){}public function rows(Collection $keywordIds,\DateTimeInterface $from,\DateTimeInterface $to):Collection{$period=$this->visibility->measurements($keywordIds,$from,$to)->groupBy('keyword_id');$before=SerpCheck::query()->whereIn('keyword_id',$keywordIds)->where('status','completed')->where('checked_at','<',$from)->with(['keyword.project','results'=>fn($q)=>$q->where('result_type','organic')->where('is_project_domain',true)->orderBy('position')])->latest('checked_at')->get()->unique('keyword_id')->keyBy('keyword_id');return $keywordIds->map(function($id)use($period,$before){$items=$period->get($id,collect())->sortBy('checked_at')->values();if($items->isEmpty())return null;$currentCheck=$items->last();$previousCheck=$items->count()>1?$items->first():$before->get($id);$current=$this->visibility->rank($currentCheck);$previous=$previousCheck?$this->visibility->rank($previousCheck):null;$first=$previousCheck===null;$entry=$previousCheck!==null&&$previous===null&&$current!==null;$gained=$previous!==null&&$current!==null&&$current<$previous?$previous-$current:null;return ['keyword'=>$currentCheck->keyword,'project'=>$currentCheck->keyword->project,'previous'=>$previous,'current'=>$current,'gained'=>$gained,'contribution'=>round(((float)$this->visibility->weight($current)-(float)$this->visibility->weight($previous))*100,2),'new_entry'=>$entry,'first_measurement'=>$first,'category'=>$this->category($previous,$current,$entry,$first),'checked_at'=>$currentCheck->checked_at];})->filter()->values();}public function category(?int$p,?int$c,bool$entry=false,bool$first=false):string{if($first)return'first_measurement';if($entry)return'new_entry';if($c===null||$p===null||$c>=$p)return'not_winner';return match(true){$c<=3&&$p>3=>'entered_top3',$c<=10&&$p>10=>'entered_top10',$c<=20&&$p>20=>'entered_top20',$c<=100&&$p>100=>'entered_top100',default=>'improved'};}public function winners(Collection$r):Collection{return$r->filter(fn($x)=>$x['gained']!==null||$x['new_entry'])->values();}public function summary(Collection$r):array{$w=$this->winners($r);$comparable=$r->where('first_measurement',false);return ['winners'=>$w->whereNotNull('gained')->count(),'gained'=>$w->sum('gained'),'top3'=>$w->where('category','entered_top3')->count(),'top10'=>$w->where('category','entered_top10')->count(),'new_entries'=>$w->where('new_entry',true)->count(),'contribution'=>$comparable->isEmpty()?null:round($w->sum('contribution')/$comparable->count(),2)];}public function distribution(Collection$r):array{$w=$this->winners($r);return ['1_3'=>$w->filter(fn($x)=>$x['gained']>=1&&$x['gained']<=3)->count(),'4_10'=>$w->filter(fn($x)=>$x['gained']>=4&&$x['gained']<=10)->count(),'11_20'=>$w->filter(fn($x)=>$x['gained']>=11&&$x['gained']<=20)->count(),'over20'=>$w->filter(fn($x)=>$x['gained']>20)->count(),'new_entry'=>$w->where('new_entry',true)->count()];}}
