<?php
declare(strict_types=1);
error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED);
ini_set('display_errors','1');
set_time_limit(300);
if (session_status() !== PHP_SESSION_ACTIVE) {
    session_start();
}
require_once __DIR__ . '/../vendor/autoload.php';
$app = require __DIR__ . '/../app/bootstrap.php';
if (($_SESSION['role'] ?? '') !== 'admin') { http_response_code(403); echo 'Access denied'; exit; }

function perfSubstr(string $text, int $start, int $length): string
{
    if (function_exists('mb_substr')) {
        return (string) mb_substr($text, $start, $length);
    }
    return (string) substr($text, $start, $length);
}

final class PerfWebProfiler {
    private object $app; private PDO $pdo; private array $in; private array $sample=[]; private ?int $pfsThread=null;
    public function __construct(object $app, array $in){ $this->app=$app; $this->pdo=$app->db->getPdo(); $this->in=$in; $this->pfsThread=$this->getPfsThreadId(); }
    public function run(): array {
        $this->sample=$this->pickSamples();
        $results=[]; foreach($this->scenarios() as $s){ $results[]=$this->runOne($s); }
        return ['meta'=>['generated_at'=>date('Y-m-d H:i:s'),'user'=>['name'=>$_SESSION['name']??'','didar_id'=>$_SESSION['didar_id']??''],'filters'=>$this->in,'sample'=>$this->sample,'pfs_thread_id'=>$this->pfsThread],'results'=>$results,'summary'=>$this->summary($results)];
    }
    private function scenarios(): array {
        $baseKpi=$this->baseKpiFilters(); $owner=(string)($this->in['owner_didar_id']??'');
        $kpiPending = $baseKpi; $kpiPending['deal_statuses']=['pending']; $kpiPending['deal_statuses_csv']='pending';
        $kpiNewLead = $baseKpi; $kpiNewLead['deal_lead_types']=['new']; $kpiNewLead['deal_lead_types_csv']='new';
        $leadBase=[]; if($owner!=='') $leadBase['owner_didar_id']=$owner;
        $dealBase=[]; if($owner!=='') $dealBase['owner_didar_id']=$owner;
        $sampleLead=$this->sample['lead']??null; $sampleDeal=$this->sample['deal']??null; $leadSearch=(string)($this->sample['lead_search']??''); $dealSearch=(string)($this->sample['deal_search']??'');
        $drDate=(string)($this->sample['kpi_drill_date']??'');
        $mk=function(string $id,string $title,string $group, callable $fn, array $meta=[]){ return array_merge(compact('id','title','group','fn'), $meta); };
        return [
            $mk('perf_dashboard','داشبورد عملکرد','dashboard', fn()=> $this->app->services->kpi->calculatePerformanceDashboard($baseKpi)),
            $mk('perf_dashboard_pending','داشبورد عملکرد - فیلتر وضعیت جاری','dashboard', fn()=> $this->app->services->kpi->calculatePerformanceDashboard($kpiPending)),
            $mk('perf_dashboard_new_lead_deals','داشبورد عملکرد - فقط معاملات لید جدید','dashboard', fn()=> $this->app->services->kpi->calculatePerformanceDashboard($kpiNewLead)),
            $mk('kpi_dashboard','داشبورد KPI','dashboard', fn()=> $this->app->services->kpi->calculateKPI($baseKpi)),
            $mk('kpi_drill_sales','Drilldown فروش کل','dashboard', fn()=> $this->app->services->kpi->getKpiDrilldown($baseKpi,'performance_cards','sales_total',['date'=>'','owner_didar_id'=>'','owner_name'=>'','product_name'=>'','failure_reason'=>'','activity_type'=>''])),
            $mk('kpi_drill_payment_day','Drilldown پرداخت روزانه نمونه','dashboard', function() use($baseKpi,$drDate){ if($drDate==='') return ['skipped'=>true]; return $this->app->services->kpi->getKpiDrilldown($baseKpi,'payment_status','total_paid_sum',['date'=>$drDate,'owner_didar_id'=>'','owner_name'=>'','product_name'=>'','failure_reason'=>'','activity_type'=>'']);}),
            $mk('leads_default','لیست لیدها - پیش‌فرض','leads', function() use($leadBase){ $f=array_merge($leadBase,['page'=>1,'per_page'=>50,'blataklif_bucket'=>'normal']); return ['count'=>$this->app->repositories->person->getLeadsCount($f),'rows'=>count((array)$this->app->services->lead->getLeads($f))]; }),
            $mk('leads_search','لیست لیدها - جستجو','leads', function() use($leadBase,$leadSearch){ $f=array_merge($leadBase,['page'=>1,'per_page'=>50,'blataklif_bucket'=>'normal','search'=>$leadSearch]); return ['count'=>$this->app->repositories->person->getLeadsCount($f),'rows'=>count((array)$this->app->services->lead->getLeads($f)),'search'=>$leadSearch]; }),
            $mk('leads_filtered','لیست لیدها - فیلتر تاریخ','leads', function() use($leadBase){ $f=array_merge($leadBase,['page'=>1,'per_page'=>50,'blataklif_bucket'=>'normal','register_date_from'=>$this->in['date_from'],'register_date_to'=>$this->in['date_to'],'activity_date_from'=>$this->in['date_from'],'activity_date_to'=>$this->in['date_to']]); return ['count'=>$this->app->repositories->person->getLeadsCount($f),'rows'=>count((array)$this->app->services->lead->getLeads($f))]; }),
            $mk('deals_default','لیست معاملات - پیش‌فرض','deals', fn()=> ['rows'=>count((array)$this->app->services->deal->getDeals(array_merge($dealBase,['limit'=>50])))] , ['probe_filters'=>array_merge($dealBase,['limit'=>50])]),
            $mk('deals_search','لیست معاملات - جستجو','deals', fn()=> ['rows'=>count((array)$this->app->services->deal->getDeals(array_merge($dealBase,['limit'=>50,'search'=>$dealSearch]))),'search'=>$dealSearch], ['probe_filters'=>array_merge($dealBase,['limit'=>50,'search'=>$dealSearch])]),
            $mk('deals_filtered','لیست معاملات - فیلتر وضعیت/تاریخ','deals', fn()=> ['rows'=>count((array)$this->app->services->deal->getDeals(array_merge($dealBase,['limit'=>50,'status'=>'Pending','register_date_from'=>$this->in['date_from'],'register_date_to'=>$this->in['date_to'],'legacy'=>'exclude'])))] , ['probe_filters'=>array_merge($dealBase,['limit'=>50,'status'=>'Pending','register_date_from'=>$this->in['date_from'],'register_date_to'=>$this->in['date_to'],'legacy'=>'exclude'])]),
            $mk('deals_filter_lost','لیست معاملات - فیلتر ناموفق','deals', fn()=> ['rows'=>count((array)$this->app->services->deal->getDeals(array_merge($dealBase,['limit'=>50,'status'=>'Lost','register_date_from'=>$this->in['date_from'],'register_date_to'=>$this->in['date_to']])))] , ['probe_filters'=>array_merge($dealBase,['limit'=>50,'status'=>'Lost','register_date_from'=>$this->in['date_from'],'register_date_to'=>$this->in['date_to']])]),
            $mk('modal_lead_profile','مودال لید - داده پروفایل','modal', function() use($sampleLead){ if(!$sampleLead) return ['skipped'=>true,'reason'=>'no_sample_lead']; return $this->simulateLeadModal((string)$sampleLead['didar_contact_id']); }),
            $mk('modal_deal','مودال معامله - داده معامله/محصول/فعالیت','modal', function() use($sampleDeal){ if(!$sampleDeal) return ['skipped'=>true,'reason'=>'no_sample_deal']; return $this->simulateDealModal((string)$sampleDeal['didar_deal_id']); }),
            $mk('modal_activity_init','مودال ثبت فعالیت - lookups/types','modal', fn()=> ['lookups_groups'=>is_array($this->app->repositories->lookup->getAllLookups(false))?count($this->app->repositories->lookup->getAllLookups(false)):0]),
        ];
    }
    private function baseKpiFilters(): array {
        $owner=trim((string)$this->in['owner_didar_id']); $owners=$owner!==''?[$owner]:[];
        return ['date_from'=>$this->in['date_from'],'date_to'=>$this->in['date_to'],'person_date_from'=>$this->in['date_from'],'person_date_to'=>$this->in['date_to'],'owner_didar_id'=>count($owners)===1?$owners[0]:'','owner_didar_ids'=>$owners,'owner_didar_ids_csv'=>implode(',',$owners),'product_id'=>'','product_ids'=>[],'product_ids_csv'=>'','deal_statuses'=>[],'deal_statuses_csv'=>'','deal_lead_types'=>[],'deal_lead_types_csv'=>'','kpi_sales_date_basis'=>$this->in['kpi_sales_date_basis'],'kpi_sales_count_rule'=>$this->in['kpi_sales_count_rule'],'kpi_payment_date_basis'=>$this->in['kpi_payment_date_basis'],'kpi_payment_count_rule'=>$this->in['kpi_payment_count_rule'],'kpi_payment_status_scope'=>$this->in['kpi_payment_status_scope'],'legacy_deal_scope'=>$this->in['legacy_deal_scope']];
    }
    private function pickSamples(): array {
        $owner=trim((string)$this->in['owner_didar_id']); $params=[]; $ownerSql=''; if($owner!==''){ $ownerSql=' AND owner_didar_id = ?'; $params[]=$owner; }
        $st=$this->pdo->prepare("SELECT id,didar_contact_id,first_name,last_name,mobile_phone,owner_didar_id FROM persons WHERE COALESCE(didar_contact_id,'')<>'' {$ownerSql} ORDER BY COALESCE(register_time,created_at) DESC LIMIT 1"); $st->execute($params); $lead=$st->fetch(PDO::FETCH_ASSOC)?:null;
        $st2=$this->pdo->prepare("SELECT id,didar_deal_id,title,owner_didar_id,contact_didar_id,status FROM deals WHERE COALESCE(didar_deal_id,'')<>'' {$ownerSql} ORDER BY COALESCE(register_time,created_at) DESC LIMIT 1"); $st2->execute($params); $deal=$st2->fetch(PDO::FETCH_ASSOC)?:null;
        $leadSearch=''; if($lead){ $leadSearch=trim((string)($lead['mobile_phone']??'')); if($leadSearch==='') $leadSearch=trim(((string)($lead['first_name']??'')).' '.((string)($lead['last_name']??''))); }
        $dealSearch=''; if($deal){ $dealSearch=trim(perfSubstr((string)($deal['title']??''),0,12)); if($dealSearch==='') $dealSearch=(string)($deal['didar_deal_id']??''); }
        $drDate=''; try { $pd=$this->app->services->kpi->calculatePerformanceDashboard($this->baseKpiFilters()); $drDate=(string)(($pd['charts']['daily_sales'][0]['date']??'')); } catch(Throwable $e) {}
        return ['lead'=>$lead,'deal'=>$deal,'lead_search'=>$leadSearch,'deal_search'=>$dealSearch,'kpi_drill_date'=>$drDate];
    }
    private function simulateLeadModal(string $contactId): array {
        $person=$this->app->repositories->person->findByDidarId($contactId) ?: $this->app->repositories->person->findByIdOrDidarId($contactId);
        if(!$person) return ['skipped'=>true,'reason'=>'lead_not_found'];
        $vsRepo=$this->app->services->virtualStage->getRepository();
        $stage=$vsRepo->getActiveStage($person->didar_contact_id) ?: $vsRepo->getSpecialStage($person->didar_contact_id) ?: $this->app->services->virtualStage->calculate($person->didar_contact_id);
        $deals=$this->app->repositories->deal->findByContactIdLegacy($person->didar_contact_id,false,50);
        $legacy=$this->app->repositories->deal->findByContactIdLegacy($person->didar_contact_id,true,50);
        $acts=$this->app->repositories->activity->findByContactId($person->didar_contact_id,50);
        return ['contact_id'=>$person->didar_contact_id,'stage'=>$stage,'deals'=>count((array)$deals),'legacy_deals'=>count((array)$legacy),'activities'=>count((array)$acts)];
    }
    private function simulateDealModal(string $dealDidarId): array {
        $deal=$this->app->services->deal->getDeal($dealDidarId); if(!$deal) return ['skipped'=>true,'reason'=>'deal_not_found'];
        $products=$this->app->repositories->deal->getProductsByDealDidarId($dealDidarId);
        $acts=$this->app->services->activity->getActivitiesByDeal($dealDidarId,200);
        return ['deal_id'=>$dealDidarId,'products'=>count((array)$products),'activities'=>count((array)$acts),'status'=>$deal->status??null];
    }
    private function runOne(array $s): array {
        $iters=max(1,(int)$this->in['iterations']); $samples=[]; $errors=[]; $shape=null;
        for($i=0;$i<$iters;$i++){
            gc_collect_cycles(); $st0=$this->sessionStatus(); $p0=$this->pfsBaseline(); $m0=memory_get_usage(true); $pk0=memory_get_peak_usage(true); $t0=microtime(true);
            try { $res=($s['fn'])(); $ok=true; if($shape===null) $shape=$this->shape($res); }
            catch(Throwable $e){ $ok=false; $res=null; $errors[]=['message'=>$e->getMessage(),'file'=>$e->getFile(),'line'=>$e->getLine()]; }
            $t1=microtime(true); $st1=$this->sessionStatus(); $pfs=$this->pfsStatements($p0);
            $samples[]=['ok'=>$ok,'wall_ms'=>round(($t1-$t0)*1000,2),'memory_delta_kb'=>round((memory_get_usage(true)-$m0)/1024,2),'peak_delta_kb'=>round((memory_get_peak_usage(true)-$pk0)/1024,2),'db_delta'=>$this->statusDiff($st0,$st1),'top_sql'=>$pfs];
        }
        $summary=$this->samplesSummary($samples);
        $out = ['id'=>$s['id'],'title'=>$s['title'],'group'=>$s['group'],'summary'=>$summary,'result_shape'=>$shape,'errors'=>$errors,'samples'=>$samples,'hints'=>$this->scenarioHints($summary,$samples,$errors)];
        if (($s['group'] ?? '') === 'deals' && !empty($s['probe_filters']) && empty($errors)) {
            try {
                $out['explain'] = $this->app->repositories->deal->explainGetAll((array)$s['probe_filters']);
            } catch (Throwable $e) {
                $out['explain_error'] = ['message'=>$e->getMessage(),'file'=>$e->getFile(),'line'=>$e->getLine()];
            }
        }
        return $out;
    }
    private function sessionStatus(): array {
        $need=['Questions','Created_tmp_tables','Created_tmp_disk_tables','Select_scan','Sort_rows','Handler_read_rnd_next','Innodb_rows_read'];
        $rows=$this->pdo->query('SHOW SESSION STATUS')->fetchAll(PDO::FETCH_ASSOC)?:[]; $m=[];
        foreach($rows as $r){ $k=(string)$r['Variable_name']; if(!in_array($k,$need,true)) continue; $m[$k]=(float)$r['Value']; }
        return $m;
    }
    private function statusDiff(array $a,array $b): array { $o=[]; foreach($b as $k=>$v){ $o[$k]=round((float)$v-(float)($a[$k]??0),2); } return $o; }
    private function getPfsThreadId(): ?int {
        try { $v=$this->pdo->query("SELECT THREAD_ID FROM performance_schema.threads WHERE PROCESSLIST_ID = CONNECTION_ID() LIMIT 1")->fetchColumn(); return $v!==false?(int)$v:null; } catch(Throwable $e){ return null; }
    }
    private function pfsBaseline(): ?array {
        if($this->pfsThread===null) return null;
        foreach(['events_statements_history_long','events_statements_history'] as $tbl){
            try{ $st=$this->pdo->prepare("SELECT COALESCE(MAX(EVENT_ID),0) FROM performance_schema.{$tbl} WHERE THREAD_ID=?"); $st->execute([$this->pfsThread]); return ['tbl'=>$tbl,'thread'=>$this->pfsThread,'eid'=>(int)$st->fetchColumn()]; }catch(Throwable $e){}
        }
        return null;
    }
    private function pfsStatements(?array $base): array {
        if(!$base) return [];
        try {
            $sql="SELECT LEFT(COALESCE(DIGEST_TEXT,SQL_TEXT),180) digest_text, COUNT(*) cnt, ROUND(SUM(TIMER_WAIT)/1000000000,2) total_ms, ROUND(MAX(TIMER_WAIT)/1000000000,2) max_ms FROM performance_schema.{$base['tbl']} WHERE THREAD_ID=? AND EVENT_ID>? AND EVENT_NAME LIKE 'statement/sql/%' AND COALESCE(SQL_TEXT,'')<>'' GROUP BY DIGEST, digest_text ORDER BY total_ms DESC LIMIT 5";
            $st=$this->pdo->prepare($sql); $st->execute([$base['thread'],$base['eid']]); $rows=$st->fetchAll(PDO::FETCH_ASSOC)?:[];
            return array_map(fn($r)=>['sql'=>(string)$r['digest_text'],'count'=>(int)$r['cnt'],'total_ms'=>(float)$r['total_ms'],'max_ms'=>(float)$r['max_ms']],$rows);
        } catch(Throwable $e) { return []; }
    }
    private function shape($res): array { if(is_array($res)) return ['type'=>'array','count'=>count($res),'keys'=>array_slice(array_map('strval',array_keys($res)),0,12)]; if(is_object($res)) return ['type'=>'object:'.get_class($res)]; return ['type'=>gettype($res)]; }
    private function samplesSummary(array $samples): array {
        $ok=array_values(array_filter($samples,fn($s)=>!empty($s['ok']))); $walls=array_map(fn($s)=>(float)$s['wall_ms'],$ok);
        $avg=count($walls)?array_sum($walls)/count($walls):0; $dbAvg=[]; foreach($ok as $s){ foreach(($s['db_delta']??[]) as $k=>$v){ $dbAvg[$k]=($dbAvg[$k]??0)+(float)$v; } } foreach($dbAvg as $k=>$v){ $dbAvg[$k]=round($v/max(1,count($ok)),2); }
        return ['ok_iters'=>count($ok),'fail_iters'=>count($samples)-count($ok),'wall_ms_avg'=>round($avg,2),'wall_ms_min'=>round((float)(count($walls)?min($walls):0),2),'wall_ms_max'=>round((float)(count($walls)?max($walls):0),2),'db_avg'=>$dbAvg];
    }
    private function scenarioHints(array $sum, array $samples, array $errors): array {
        $h=[]; if($errors){ $h[]='خطا در سناریو؛ ابتدا خطا را رفع کنید.'; return $h; }
        if(($sum['wall_ms_avg']??0)>1000) $h[]='بیش از ۱ ثانیه؛ گلوگاه جدی.'; elseif(($sum['wall_ms_avg']??0)>300) $h[]='بیش از ۳۰۰ms؛ بهینه‌سازی query/cache مؤثر است.';
        if((($sum['db_avg']['Questions']??0)>50)) $h[]='Questions بالا؛ query fan-out یا محاسبات تکراری.';
        if((($sum['db_avg']['Created_tmp_disk_tables']??0)>0)) $h[]='tmp disk table ساخته شد؛ احتمال index ناکافی یا group/sort سنگین.';
        $top=$samples[0]['top_sql'][0]['sql']??''; if($top!=='' && stripos($top,'json_table')!==false) $h[]='JSON_TABLE در top SQL؛ لایه aggregate/canonical پرداخت پیشنهاد می‌شود.';
        return $h;
    }
    private function summary(array $results): array {
        $sorted=$results; usort($sorted, fn($a,$b)=> (($b['summary']['wall_ms_avg']??0)<=>($a['summary']['wall_ms_avg']??0)));
        $groups=[]; foreach($results as $r){ $g=$r['group']; $groups[$g]['count']=($groups[$g]['count']??0)+1; $groups[$g]['sum']=($groups[$g]['sum']??0)+(float)($r['summary']['wall_ms_avg']??0); $groups[$g]['max']=max((float)($groups[$g]['max']??0),(float)($r['summary']['wall_ms_avg']??0)); }
        foreach($groups as $g=>&$v){ $v=['scenarios'=>$v['count'],'avg_ms'=>round($v['sum']/max(1,$v['count']),2),'max_ms'=>round($v['max'],2)]; }
        $sqlAgg=[]; foreach($results as $r){ foreach(($r['samples']??[]) as $s){ foreach(($s['top_sql']??[]) as $q){ $k=(string)($q['sql']??''); if($k==='') continue; if(!isset($sqlAgg[$k])) $sqlAgg[$k]=['sql'=>$k,'samples'=>0,'total_ms'=>0.0,'max_ms'=>0.0]; $sqlAgg[$k]['samples']++; $sqlAgg[$k]['total_ms']+=(float)($q['total_ms']??0); $sqlAgg[$k]['max_ms']=max($sqlAgg[$k]['max_ms'], (float)($q['max_ms']??0)); } } }
        $sqlAgg=array_values($sqlAgg); usort($sqlAgg, fn($a,$b)=> (($b['total_ms']<=>$a['total_ms']) ?: ($b['samples']<=>$a['samples'])));
        $h=[]; if(array_filter($results, fn($r)=>$r['group']==='dashboard' && (($r['summary']['wall_ms_avg']??0)>500))) $h[]='داشبوردها کند هستند؛ Lazy-load cards/charts/tables + cache کوتاه‌مدت KPI بیشترین اثر را دارد.';
        if(array_filter($results, fn($r)=>(($r['summary']['db_avg']['Select_scan']??0)>0))) $h[]='Select_scan دیده شد؛ EXPLAIN و index tuning لازم است.';
        if(array_filter($results, fn($r)=>$r['id']==='deals_default' && (($r['summary']['wall_ms_avg']??0)>3000))) $h[]='deals_default بسیار کند است؛ Deferred Join فعال شده، قدم بعدی EXPLAIN + index + cursor/seek pagination در UI است.';
        return ['slowest'=>array_map(fn($r)=>['id'=>$r['id'],'title'=>$r['title'],'group'=>$r['group'],'avg_ms'=>$r['summary']['wall_ms_avg']??0,'questions'=>$r['summary']['db_avg']['Questions']??0], array_slice($sorted,0,10)),'groups'=>$groups,'top_sql_hotspots'=>array_slice(array_map(fn($q)=>['sql'=>$q['sql'],'samples'=>$q['samples'],'total_ms'=>round($q['total_ms'],2),'max_ms'=>round($q['max_ms'],2)],$sqlAgg),0,10),'global_hints'=>$h];
    }
}

function profilerInput(): array { $d=['date_from'=>date('Y-m-01'),'date_to'=>date('Y-m-d'),'owner_didar_id'=>'','iterations'=>1,'kpi_sales_date_basis'=>'ui_payment_date','kpi_sales_count_rule'=>'first_payment_only','kpi_payment_date_basis'=>'ui_payment_date','kpi_payment_count_rule'=>'all_payments','kpi_payment_status_scope'=>'confirmed_only','legacy_deal_scope'=>'exclude','format'=>'html']; $src=$_SERVER['REQUEST_METHOD']==='POST'?$_POST:$_GET; foreach($d as $k=>$v){ if(isset($src[$k])) $d[$k]=is_int($v)?(int)$src[$k]:(string)$src[$k]; } $d['iterations']=max(1,min(5,(int)$d['iterations'])); return $d; }
function h($v): string { return htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8'); }
$in=profilerInput(); $report=null; $err=null;
if(($_SERVER['REQUEST_METHOD']==='POST' && isset($_POST['run_profiler'])) || (($_GET['autorun']??'')==='1')){ try{ $report=(new PerfWebProfiler($app,$in))->run(); } catch(Throwable $e){ $err=['message'=>$e->getMessage(),'file'=>$e->getFile(),'line'=>$e->getLine()]; } }
if(($in['format']??'html')==='json'){
    header('Content-Type: application/json; charset=utf-8');
    if($report){ echo json_encode(['status'=>'success','report'=>$report],JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT); }
    else { echo json_encode(['status'=>'error','error'=>$err?:['message'=>'No report generated']],JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT); }
    exit;
}
?>
<!doctype html><html lang="fa" dir="rtl"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Profiler KPI</title><style>
body{font-family:tahoma;background:#0f172a;color:#e2e8f0;margin:0;padding:16px} .card{background:#111827;border:1px solid #334155;border-radius:10px;padding:12px;margin-bottom:12px}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:8px} label{display:block;font-size:12px;color:#cbd5e1;margin-bottom:4px} input,select,button{width:100%;padding:8px;border-radius:8px;border:1px solid #334155;background:#0b1220;color:#e2e8f0;box-sizing:border-box} button{background:#1d4ed8;border-color:#1d4ed8;cursor:pointer} table{width:100%;border-collapse:collapse;font-size:12px} th,td{border:1px solid #334155;padding:6px;vertical-align:top} th{background:#1f2937} .muted{color:#94a3b8} .warn{color:#fbbf24} pre{white-space:pre-wrap;word-break:break-word;background:#0b1220;border:1px solid #334155;padding:8px;border-radius:8px;max-height:360px;overflow:auto}
</style></head><body>
<div class="card"><h2 style="margin:0 0 8px">Profiler سناریوهای KPI/Performance/Leads/Deals/Modals</h2><div class="muted" style="font-size:12px;margin-bottom:8px">این ابزار زمان سناریوهای اصلی را روی سرور اندازه می‌گیرد (DB session metrics + top SQL best-effort). سرعت animation مودال اندازه‌گیری نمی‌شود؛ زمان بارگذاری داده مودال سنجیده می‌شود.</div>
<form method="post"><div class="grid">
<div><label>از تاریخ</label><input type="date" name="date_from" value="<?=h($in['date_from'])?>"></div>
<div><label>تا تاریخ</label><input type="date" name="date_to" value="<?=h($in['date_to'])?>"></div>
<div><label>Owner Didar ID (اختیاری)</label><input type="text" name="owner_didar_id" value="<?=h($in['owner_didar_id'])?>"></div>
<div><label>تکرار هر سناریو</label><input type="number" min="1" max="5" name="iterations" value="<?=h((string)$in['iterations'])?>"></div>
<div><label>مبنای تاریخ فروش</label><select name="kpi_sales_date_basis"><option value="ui_payment_date" <?=$in['kpi_sales_date_basis']==='ui_payment_date'?'selected':''?>>UI</option><option value="db_payment_date" <?=$in['kpi_sales_date_basis']==='db_payment_date'?'selected':''?>>DB</option><option value="system_created_at" <?=$in['kpi_sales_date_basis']==='system_created_at'?'selected':''?>>System</option></select></div>
<div><label>قاعده احتساب فروش</label><select name="kpi_sales_count_rule"><option value="first_payment_only" <?=$in['kpi_sales_count_rule']==='first_payment_only'?'selected':''?>>فقط اولین واریزی</option><option value="all_payments" <?=$in['kpi_sales_count_rule']==='all_payments'?'selected':''?>>همه پرداخت‌ها</option></select></div>
<div><label>مبنای تاریخ مالی</label><select name="kpi_payment_date_basis"><option value="ui_payment_date" <?=$in['kpi_payment_date_basis']==='ui_payment_date'?'selected':''?>>UI</option><option value="db_payment_date" <?=$in['kpi_payment_date_basis']==='db_payment_date'?'selected':''?>>DB</option><option value="system_created_at" <?=$in['kpi_payment_date_basis']==='system_created_at'?'selected':''?>>System</option></select></div>
<div><label>قاعده احتساب مالی</label><select name="kpi_payment_count_rule"><option value="all_payments" <?=$in['kpi_payment_count_rule']==='all_payments'?'selected':''?>>همه پرداخت‌ها</option><option value="first_payment_only" <?=$in['kpi_payment_count_rule']==='first_payment_only'?'selected':''?>>فقط اولین واریزی</option></select></div>
<div><label>وضعیت پرداخت</label><select name="kpi_payment_status_scope"><option value="confirmed_only" <?=$in['kpi_payment_status_scope']==='confirmed_only'?'selected':''?>>فقط تاییدشده</option><option value="all_statuses" <?=$in['kpi_payment_status_scope']==='all_statuses'?'selected':''?>>همه</option></select></div>
<div><label>Legacy</label><select name="legacy_deal_scope"><option value="exclude" <?=$in['legacy_deal_scope']==='exclude'?'selected':''?>>حذف</option><option value="all" <?=$in['legacy_deal_scope']==='all'?'selected':''?>>همه</option><option value="only" <?=$in['legacy_deal_scope']==='only'?'selected':''?>>فقط legacy</option></select></div>
</div><div style="display:flex;gap:8px;margin-top:8px"><button type="submit" name="run_profiler" value="1">اجرای پروفایل</button><button type="submit" onclick="this.form.format.value='json'">خروجی JSON</button><input type="hidden" name="format" value="html"></div></form></div>
<?php if($err): ?><div class="card"><div style="color:#f87171">خطا: <?=h($err['message'])?></div><div class="muted"><?=h(($err['file']??'').':'.($err['line']??''))?></div></div><?php endif; ?>
<?php if($report): ?><div class="card"><h3 style="margin-top:0">خلاصه</h3>
<div class="muted">زمان اجرا: <?=h($report['meta']['generated_at'])?> | PFS Thread: <?=h((string)($report['meta']['pfs_thread_id']??''))?></div>
<?php foreach(($report['summary']['global_hints']??[]) as $hint): ?><div class="warn">• <?=h($hint)?></div><?php endforeach; ?>
<table><thead><tr><th>سناریو</th><th>گروه</th><th>Avg ms</th><th>Questions</th></tr></thead><tbody><?php foreach(($report['summary']['slowest']??[]) as $r): ?><tr><td><?=h($r['title'])?><div class="muted"><?=h($r['id'])?></div></td><td><?=h($r['group'])?></td><td><?=h((string)$r['avg_ms'])?></td><td><?=h((string)$r['questions'])?></td></tr><?php endforeach; ?></tbody></table></div>
<?php if(!empty($report['summary']['top_sql_hotspots'])): ?><div class="card"><h3 style="margin-top:0">Top SQL Hotspots (تجمیعی)</h3><table><thead><tr><th>SQL</th><th>نمونه‌ها</th><th>Total ms</th><th>Max ms</th></tr></thead><tbody><?php foreach(($report['summary']['top_sql_hotspots']??[]) as $q): ?><tr><td class="muted"><?=h($q['sql'])?></td><td><?=h((string)$q['samples'])?></td><td><?=h((string)$q['total_ms'])?></td><td><?=h((string)$q['max_ms'])?></td></tr><?php endforeach; ?></tbody></table></div><?php endif; ?>
<div class="card"><h3 style="margin-top:0">جزئیات سناریوها</h3><table><thead><tr><th>سناریو</th><th>Avg/Min/Max</th><th>DB metrics avg</th><th>Top SQL / EXPLAIN</th><th>Hints</th></tr></thead><tbody><?php foreach(($report['results']??[]) as $r): $s=$r['summary']??[]; $db=$s['db_avg']??[]; $sql=$r['samples'][0]['top_sql'][0]??null; $ex=$r['explain']['page_selection']['explain']['format']??''; ?><tr><td><b><?=h($r['title'])?></b><div class="muted"><?=h($r['id'])?></div><?php if(!empty($r['errors'])): ?><div style="color:#f87171"><?=h($r['errors'][0]['message']??'')?></div><?php endif; ?><?php if(!empty($r['explain_error'])): ?><div style="color:#f87171"><?=h($r['explain_error']['message']??'')?></div><?php endif; ?></td><td><?=h((string)($s['wall_ms_avg']??0))?> / <?=h((string)($s['wall_ms_min']??0))?> / <?=h((string)($s['wall_ms_max']??0))?></td><td>Q: <?=h((string)($db['Questions']??0))?><br>Scan: <?=h((string)($db['Select_scan']??0))?><br>TmpDisk: <?=h((string)($db['Created_tmp_disk_tables']??0))?><br>RowsRead: <?=h((string)($db['Innodb_rows_read']??0))?></td><td><?php if($sql): ?><div><b><?=h((string)($sql['total_ms']??0))?>ms</b> / <?=h((string)($sql['count']??0))?>x</div><div class="muted"><?=h((string)($sql['sql']??''))?></div><?php else: ?><span class="muted">n/a</span><?php endif; ?><?php if(!empty($r['explain'])): ?><div class="muted" style="margin-top:6px">EXPLAIN(page): <?=h((string)$ex)?> | rows_selected=<?=h((string)($r['explain']['page_selection']['rows_selected']??0))?></div><?php endif; ?></td><td><?php foreach(($r['hints']??[]) as $h0): ?><div class="warn">• <?=h($h0)?></div><?php endforeach; ?></td></tr><?php endforeach; ?></tbody></table></div>
<?php if($report): foreach(($report['results']??[]) as $r): if(empty($r['explain'])) continue; ?>
<div class="card"><h3 style="margin-top:0">EXPLAIN — <?=h($r['title'])?></h3><pre><?=h(json_encode($r['explain'],JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT))?></pre></div>
<?php endforeach; endif; ?>
<div class="card"><h3 style="margin-top:0">JSON کامل</h3><pre><?=h(json_encode($report,JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT))?></pre></div><?php endif; ?>
</body></html>
