<?php
class V44RankingOptimizerEngine {
    private $repo;

    public function __construct(V44ProductionRepository $repo) {
        $this->repo = $repo;
    }

    private function n($v,$d=0){ return is_numeric($v) ? (float)$v : $d; }
    private function i($v,$d=0){ return is_numeric($v) ? (int)$v : $d; }
    private function clamp($v,$min,$max){ return max($min,min($max,$v)); }
    private function runKey(){ return date('YmdHis').'_'.mt_rand(1000,9999); }

    private function grade($s){
        $s=$this->n($s);
        if($s>=92)return 'S+';
        if($s>=84)return 'S';
        if($s>=76)return 'A';
        if($s>=66)return 'B';
        if($s>=54)return 'C';
        if($s>=40)return 'D';
        return 'E';
    }

    private function decision($s,$risk){
        $s=$this->n($s); $risk=$this->n($risk);
        if($s>=86 && $risk<50)return 'BUY';
        if($s>=70)return 'COVER';
        if($s>=50)return 'WATCH';
        return 'SKIP';
    }

    private function role($rank,$score,$gap,$risk){
        if($rank==1 && $score>=70 && $risk<58) return '主席膽';
        if($rank<=2 && $score>=62) return 'Q膽';
        if($rank<=4 && $score>=52) return '主力腳';
        if($rank<=6 && $gap<=16) return '冷門腳';
        return '觀察';
    }

    private function state($s,$stability,$risk){
        if($s>=88&&$stability>=70&&$risk<45)return 'PRODUCTION_CORE';
        if($s>=76)return 'PRODUCTION_STRONG';
        if($s>=64)return 'PRODUCTION_LIVE';
        if($s>=52)return 'PRODUCTION_WATCH';
        if($risk>=70)return 'PRODUCTION_RISK';
        return 'PRODUCTION_WEAK';
    }

    public function optimize(array $source, $run, $mode='BALANCED') {
        $by=array();
        foreach($source as $r){
            $g=$r['race_date'].'|'.$r['venue'].'|'.$r['race_num'];
            if(!isset($by[$g])) $by[$g]=array();
            $by[$g][]=$r;
        }

        $prod=array(); $opt=array(); $apis=array();

        foreach($by as $g=>$arr){
            $scores=array();
            foreach($arr as $r){ $scores[]=$this->n(isset($r['production_score'])?$r['production_score']:0); }
            $avg=count($scores)?array_sum($scores)/count($scores):0;
            $max=count($scores)?max($scores):0;
            $entropy=$this->clamp(100-($max-$avg)*1.25,0,100);
            $strength=$this->clamp($avg*0.38+$max*0.42+(100-$entropy)*0.20,0,100);

            $tmp=array();
            foreach($arr as $r){
                $v43=$this->n(isset($r['production_score'])?$r['production_score']:(isset($r['horse_score'])?$r['horse_score']:0));
                $win=$this->n(isset($r['win_score'])?$r['win_score']:0);
                $place=$this->n(isset($r['place_score'])?$r['place_score']:0);
                $q=$this->n(isset($r['q_score'])?$r['q_score']:0);
                $trio=$this->n(isset($r['trio_score'])?$r['trio_score']:0);
                $risk=$this->n(isset($r['risk_score'])?$r['risk_score']:0);
                $conf=$this->n(isset($r['confidence_score'])?$r['confidence_score']:50);
                $chair=$this->n(isset($r['chairman_score'])?$r['chairman_score']:50);
                $marketBalance=$this->clamp(($win*0.25+$place*0.25+$q*0.25+$trio*0.25),0,100);
                $modeAdj=0;
                if($mode==='AGGRESSIVE') $modeAdj=($win-$risk)*0.04;
                elseif($mode==='CONSERVATIVE') $modeAdj=($place+$q-$risk)*0.035;
                elseif($mode==='DEFENSIVE') $modeAdj=(100-$risk)*0.05;
                $optimized=$this->clamp($v43*0.42+$marketBalance*0.20+$chair*0.16+$conf*0.12+(100-$risk)*0.10+$modeAdj,0,100);
                $stability=$this->clamp($conf*0.38+(100-$risk)*0.30+$place*0.16+$q*0.16,0,100);
                $calibrated=$this->clamp($optimized*0.72+$stability*0.18+($strength-$entropy)*0.04,0,100);
                $tmp[]=array('src'=>$r,'v43'=>$v43,'optimized'=>$optimized,'calibrated'=>$calibrated,'stability'=>$stability,'risk'=>$risk,'conf'=>$conf,'win'=>$win,'place'=>$place,'q'=>$q,'trio'=>$trio,'chair'=>$chair);
            }

            usort($tmp,function($a,$b){
                if($a['calibrated']==$b['calibrated']) return 0;
                return $a['calibrated']<$b['calibrated']?1:-1;
            });

            $top=count($tmp)?$tmp[0]['calibrated']:0;
            $second=count($tmp)>1?$tmp[1]['calibrated']:0;
            $fourth=count($tmp)>3?$tmp[3]['calibrated']:$second;
            $total=0;
            foreach($tmp as $x){ $total+=max(1,$x['calibrated']); }

            $rank=1; $apiHorses=array(); $qPick=array(); $trioPick=array(); $sixPick=array(); $avoid=array();

            foreach($tmp as $x){
                $r=$x['src'];
                $gap=$top-$x['calibrated'];
                $role=$this->role($rank,$x['calibrated'],$gap,$x['risk']);
                $decision=$this->decision($x['calibrated'],$x['risk']);
                $grade=$this->grade($x['calibrated']);
                $state=$this->state($x['calibrated'],$x['stability'],$x['risk']);
                $prob=$total>0?$this->clamp($x['calibrated']/$total*100,0,100):0;

                if($rank<=2) $qPick[]=$r['horse_num'];
                if($rank<=4) $trioPick[]=$r['horse_num'];
                if($rank<=2) $sixPick[]=$r['horse_num'];
                if($x['risk']>=68 || $decision==='SKIP') $avoid[]=$r['horse_num'];

                $payload=array(
                    'race_date'=>$r['race_date'],'venue'=>$r['venue'],'race_num'=>$this->i($r['race_num']),
                    'horse_num'=>$r['horse_num'],'horse_name'=>$r['horse_name'],
                    'chairman_final_score'=>round($x['calibrated'],2),'rank'=>$rank,'role'=>$role,
                    'decision'=>$decision,'grade'=>$grade,'state'=>$state,'risk'=>round($x['risk'],2),
                    'confidence'=>round($x['conf'],2),'win'=>round($x['win'],2),'place'=>round($x['place'],2),
                    'q'=>round($x['q'],2),'trio'=>round($x['trio'],2)
                );

                $prod[]=array(
                    'production_key'=>sha1($run.'|PROD|'.$r['race_date'].'|'.$r['venue'].'|'.$r['race_num'].'|'.$r['horse_num']),
                    'run_key'=>$run,'race_date'=>$r['race_date'],'venue'=>$r['venue'],'race_num'=>$this->i($r['race_num']),
                    'horse_num'=>(string)$r['horse_num'],'horse_name'=>(string)$r['horse_name'],
                    'v43_score'=>$x['v43'],'optimized_score'=>$x['optimized'],'calibrated_score'=>$x['calibrated'],
                    'win_score'=>$x['win'],'place_score'=>$x['place'],'q_score'=>$x['q'],'trio_score'=>$x['trio'],
                    'risk_score'=>$x['risk'],'confidence_score'=>$x['conf'],'stability_score'=>$x['stability'],
                    'race_rank'=>$rank,'role_tag'=>$role,'final_decision'=>$decision,'production_grade'=>$grade,
                    'production_state'=>$state,'score_gap'=>$gap,'top_probability'=>$prob,
                    'api_payload'=>json_encode($payload,JSON_UNESCAPED_UNICODE),
                    'reason_zh'=>'V44 Migrated Engine：#'.$r['horse_num'].' '.$r['horse_name'].' Rank #'.$rank.'，Final='.number_format($x['calibrated'],1).'，V43='.number_format($x['v43'],1).'，Stability='.number_format($x['stability'],1).'，Gap='.number_format($gap,1).'。',
                    'trace'=>'V44_MIGRATED_ENGINE|R'.$r['race_num'].'|H'.$r['horse_num'].'|rank='.$rank
                );
                $apiHorses[]=$payload;
                $rank++;
            }

            list($rd,$ve,$rn)=explode('|',$g);
            $raceMode=$entropy>=72?'混戰場':(($top-$second)>=10?'主席膽場':'主力腳場');
            $hint=$raceMode==='主席膽場'?'TOP_PICK_ALLOWED':($raceMode==='主力腳場'?'Q_TRIO_PRIORITY':'WIDE_COVER_REQUIRED');
            $json=array('run_key'=>$run,'race_date'=>$rd,'venue'=>$ve,'race_num'=>$this->i($rn),'race_mode'=>$raceMode,'race_hint'=>$hint,'top_horse'=>isset($tmp[0])?$tmp[0]['src']['horse_num']:'','gap12'=>$top-$second,'horses'=>$apiHorses,'q_suggestion'=>$qPick,'trio_suggestion'=>$trioPick,'sixup_suggestion'=>$sixPick,'avoid'=>$avoid);

            $opt[]=array(
                'optimizer_key'=>sha1($run.'|OPT|'.$g),'run_key'=>$run,'race_date'=>$rd,'venue'=>$ve,'race_num'=>$this->i($rn),
                'horse_count'=>count($tmp),'race_strength'=>$strength,'race_stability'=>$this->clamp(100-$entropy+($top-$second)*2,0,100),
                'race_entropy'=>$entropy,'top_horse'=>isset($tmp[0])?$tmp[0]['src']['horse_num']:'',
                'top_score'=>$top,'second_horse'=>isset($tmp[1])?$tmp[1]['src']['horse_num']:'',
                'second_score'=>$second,'gap12'=>$top-$second,'gap14'=>$top-$fourth,'race_mode'=>$raceMode,'race_hint'=>$hint,
                'q_suggestion'=>implode(',',$qPick),'trio_suggestion'=>implode(',',$trioPick),
                'sixup_suggestion'=>implode(',',$sixPick),'avoid_suggestion'=>implode(',',$avoid),
                'json_payload'=>json_encode($json,JSON_UNESCAPED_UNICODE),
                'reason_zh'=>'V44 Migrated Race Optimizer：R'.$rn.' '.$raceMode.'，Top='.($json['top_horse']).'，Gap12='.number_format($top-$second,1).'。',
                'trace'=>'V44_MIGRATED_RACE_OPT|R'.$rn.'|'.$hint
            );

            $apis[]=array(
                'api_key'=>sha1($run.'|API|'.$g),'run_key'=>$run,'race_date'=>$rd,'venue'=>$ve,'race_num'=>$this->i($rn),
                'api_type'=>'ANALYSIS_PHP_READY',
                'json_payload'=>json_encode($json,JSON_UNESCAPED_UNICODE),
                'php_hint'=>"// Framework v3.0 migrated V44 engine output\n// Read wr_chairman_production_score_v44 by race_date / venue / race_num\n",
                'reason_zh'=>'Framework v3.0：analysis.php 可讀取 migrated V44 Production Output。'
            );
        }

        return array($prod,$opt,$apis);
    }

    public function parameterLearning(array $prod, $run) {
        $weights=array('V43_SCORE_WEIGHT'=>0.42,'MARKET_BALANCE_WEIGHT'=>0.20,'CHAIRMAN_WEIGHT'=>0.16,'CONFIDENCE_WEIGHT'=>0.12,'RISK_CONTROL_WEIGHT'=>0.10);
        $avgRisk=0; $avgConf=0; $n=max(1,count($prod));
        foreach($prod as $p){ $avgRisk+=$this->n($p['risk_score']); $avgConf+=$this->n($p['confidence_score']); }
        $avgRisk/=$n; $avgConf/=$n;

        $out=array();
        foreach($weights as $name=>$old){
            $delta=0;
            if($name==='RISK_CONTROL_WEIGHT' && $avgRisk>55) $delta=0.03;
            if($name==='CONFIDENCE_WEIGHT' && $avgConf>65) $delta=0.02;
            if($name==='MARKET_BALANCE_WEIGHT' && $avgRisk>65) $delta=-0.02;
            $new=$this->clamp($old+$delta,0.05,0.60);
            $out[]=array(
                'param_key'=>sha1($run.'|PARAM|'.$name),'run_key'=>$run,'parameter_name'=>$name,
                'old_weight'=>$old,'suggested_weight'=>$new,'learning_delta'=>$new-$old,
                'sample_rows'=>count($prod),'confidence_score'=>$this->clamp($avgConf,0,100),
                'reason_zh'=>'Framework v3.0 Parameter Learning：'.$name.' old='.number_format($old,2).' → suggested='.number_format($new,2).'。',
                'trace'=>'V44_MIGRATED_PARAM|'.$name
            );
        }
        return $out;
    }

    public function preview($raceDate, $venue='', $raceNum=0, $mode='BALANCED') {
        $this->repo->ensureTables();
        $source=$this->repo->sourceRows($raceDate,$venue,$raceNum);
        $run='PREVIEW_'.date('YmdHis');
        list($prod,$opt,$apis)=$this->optimize($source,$run,$mode);
        $params=$this->parameterLearning($prod,$run);
        return array('status'=>'PREVIEW','run_key'=>$run,'source_rows'=>count($source),'production_rows'=>count($prod),'optimizer_rows'=>count($opt),'api_rows'=>count($apis),'param_rows'=>count($params),'production'=>$prod,'optimizer'=>$opt,'api'=>$apis,'params'=>$params);
    }

    public function build($raceDate, $venue='', $raceNum=0, $mode='BALANCED') {
        $this->repo->ensureTables();
        $source=$this->repo->sourceRows($raceDate,$venue,$raceNum);
        $run=$this->runKey();
        list($prod,$opt,$apis)=$this->optimize($source,$run,$mode);
        $params=$this->parameterLearning($prod,$run);

        $this->repo->begin();
        $c=array('prod'=>0,'opt'=>0,'api'=>0,'param'=>0);

        foreach($prod as $r){
            if($this->repo->insertOrUpdate('wr_chairman_production_score_v44',$r,array('v43_score','optimized_score','calibrated_score','win_score','place_score','q_score','trio_score','risk_score','confidence_score','stability_score','race_rank','role_tag','final_decision','production_grade','production_state','score_gap','top_probability','api_payload','reason_zh','trace'))) $c['prod']++;
        }
        foreach($opt as $r){
            if($this->repo->insertOrUpdate('wr_chairman_race_optimizer_v44',$r,array('horse_count','race_strength','race_stability','race_entropy','top_horse','top_score','second_horse','second_score','gap12','gap14','race_mode','race_hint','q_suggestion','trio_suggestion','sixup_suggestion','avoid_suggestion','json_payload','reason_zh','trace'))) $c['opt']++;
        }
        foreach($apis as $r){
            if($this->repo->insertOrUpdate('wr_chairman_analysis_api_v44',$r,array('json_payload','php_hint','reason_zh'))) $c['api']++;
        }
        foreach($params as $r){
            if($this->repo->insertOrUpdate('wr_chairman_parameter_learning_v44',$r,array('old_weight','suggested_weight','learning_delta','sample_rows','confidence_score','reason_zh','trace'))) $c['param']++;
        }

        $top=''; $score=0;
        if($prod){ $top=$prod[0]['horse_num']; $score=$this->n($prod[0]['calibrated_score']); }

        $msg='Framework v3.0 Migrated V44 Build: production '.$c['prod'].' | races '.$c['opt'].' | api '.$c['api'].' | params '.$c['param'].' | top '.$top.' score '.number_format($score,1);

        $this->repo->insertOrUpdate('wr_chairman_production_run_log_v44',array(
            'run_key'=>$run,'race_date'=>$raceDate,'venue'=>$venue,'context_mode'=>$mode,'status'=>'DONE',
            'source_rows'=>count($source),'production_rows'=>$c['prod'],'optimizer_rows'=>$c['opt'],'api_rows'=>$c['api'],'param_rows'=>$c['param'],
            'top_horse'=>$top,'top_score'=>$score,'message'=>$msg
        ),array());

        $this->repo->commit();

        return array('status'=>'BUILT','run_key'=>$run,'counts'=>$c,'message'=>$msg,'source_rows'=>count($source));
    }
}
