<?php
/**
 * Payment report service v3 - robust collector.
 * Fixes empty reports by reading every real source: SQL bot_payments, manual receipts,
 * Zibal JSON, managed SQLJSON shop-factor, and physical legacy JSON files.
 */

if(!function_exists('bot_payment_report_cache_dir')){
    function bot_payment_report_cache_dir(){
        $dir = defined('BOT_ROOT_DIR') ? BOT_ROOT_DIR . '/cache/reports' : bot_payment_report_root_dir() . '/cache/reports';
        if(!is_dir($dir)) @mkdir($dir, 0775, true);
        return $dir;
    }
}

if(!function_exists('bot_payment_report_root_dir')){
    function bot_payment_report_root_dir(){
        if(defined('BOT_ROOT_DIR')) return rtrim((string)BOT_ROOT_DIR, '/');
        return rtrim(dirname(__DIR__, 2), '/');
    }
}

if(!function_exists('bot_payment_report_roots')){
    function bot_payment_report_roots(){
        $roots = [];
        $roots[] = bot_payment_report_root_dir();
        $roots[] = getcwd();
        if(isset($_SERVER['DOCUMENT_ROOT']) && $_SERVER['DOCUMENT_ROOT']) $roots[] = rtrim((string)$_SERVER['DOCUMENT_ROOT'], '/').'/telegram';
        $out = [];
        foreach($roots as $r){
            $r = rtrim(str_replace('\\','/', (string)$r), '/');
            if($r !== '' && is_dir($r) && !in_array($r, $out, true)) $out[] = $r;
        }
        return $out;
    }
}

if(!function_exists('bot_payment_report_normalize_ts')){
    function bot_payment_report_normalize_ts($v){
        if(is_int($v) || ctype_digit((string)$v)){
            $n = (int)$v;
            if($n > 1000000000000) return (int)floor($n/1000);
            return $n;
        }
        $s = trim((string)$v);
        if($s === '' || $s === '-') return 0;
        $s = strtr($s, ['۰'=>'0','۱'=>'1','۲'=>'2','۳'=>'3','۴'=>'4','۵'=>'5','۶'=>'6','۷'=>'7','۸'=>'8','۹'=>'9','٠'=>'0','١'=>'1','٢'=>'2','٣'=>'3','٤'=>'4','٥'=>'5','٦'=>'6','٧'=>'7','٨'=>'8','٩'=>'9']);
        $t = strtotime($s);
        return $t ? (int)$t : 0;
    }
}

if(!function_exists('bot_payment_report_date')){
    function bot_payment_report_date($ts){
        $ts = (int)$ts;
        return $ts > 0 ? date('Y-m-d H:i', $ts) : '-';
    }
}

if(!function_exists('bot_payment_report_fa_date')){
    function bot_payment_report_fa_date($ts){
        $ts = (int)$ts;
        if($ts <= 0) return '-';
        if(function_exists('jdate')) return jdate('Y/m/d H:i', $ts);
        return date('Y/m/d H:i', $ts);
    }
}

if(!function_exists('bot_payment_report_money')){
    function bot_payment_report_money($amount){ return number_format((int)$amount); }
}

if(!function_exists('bot_payment_report_str')){
    function bot_payment_report_str($v){
        if(is_array($v) || is_object($v)) return json_encode($v, JSON_UNESCAPED_UNICODE);
        return trim((string)$v);
    }
}

if(!function_exists('bot_payment_report_json_data')){
    function bot_payment_report_json_data($raw){
        if(is_array($raw)) return $raw;
        if(is_string($raw) && trim($raw) !== ''){
            $j = json_decode($raw, true);
            return is_array($j) ? $j : [];
        }
        return [];
    }
}

if(!function_exists('bot_payment_report_sql_table_exists')){
    function bot_payment_report_sql_table_exists($pdo, $table){
        try{ $st = $pdo->prepare('SHOW TABLES LIKE ?'); $st->execute([(string)$table]); return (bool)$st->fetchColumn(); }
        catch(Throwable $e){ return false; }
    }
}

if(!function_exists('bot_payment_report_source_add')){
    function bot_payment_report_source_add($name, $count, $note=''){
        if(!isset($GLOBALS['__bot_payment_report_sources']) || !is_array($GLOBALS['__bot_payment_report_sources'])) $GLOBALS['__bot_payment_report_sources'] = [];
        $GLOBALS['__bot_payment_report_sources'][] = ['name'=>(string)$name, 'count'=>(int)$count, 'note'=>(string)$note];
        return true;
    }
}

if(!function_exists('bot_payment_report_sources_get')){
    function bot_payment_report_sources_get(){
        return isset($GLOBALS['__bot_payment_report_sources']) && is_array($GLOBALS['__bot_payment_report_sources']) ? $GLOBALS['__bot_payment_report_sources'] : [];
    }
}

if(!function_exists('bot_payment_report_sources_reset')){
    function bot_payment_report_sources_reset(){ $GLOBALS['__bot_payment_report_sources'] = []; }
}

if(!function_exists('bot_payment_report_first')){
    function bot_payment_report_first(array $arr, array $keys, $default=''){
        foreach($keys as $k){
            if(array_key_exists($k, $arr) && $arr[$k] !== null && $arr[$k] !== '') return $arr[$k];
        }
        return $default;
    }
}

if(!function_exists('bot_payment_report_amount')){
    function bot_payment_report_amount($v){
        if(is_array($v) || is_object($v)) return 0;
        $s = strtr((string)$v, ['۰'=>'0','۱'=>'1','۲'=>'2','۳'=>'3','۴'=>'4','۵'=>'5','۶'=>'6','۷'=>'7','۸'=>'8','۹'=>'9','٠'=>'0','١'=>'1','٢'=>'2','٣'=>'3','٤'=>'4','٥'=>'5','٦'=>'6','٧'=>'7','٨'=>'8','٩'=>'9']);
        $s = preg_replace('/[^0-9\-]/', '', $s);
        return (int)$s;
    }
}

if(!function_exists('bot_payment_report_status_key')){
    function bot_payment_report_status_key($status){
        $s = trim((string)$status);
        $s = str_replace(['ي','ك','أ','إ'], ['ی','ک','ا','ا'], $s);
        $low = strtolower($s);
        $low = preg_replace('/\s+/u', '', $low);
        $map = [
            'approved'=>'paid','approve'=>'paid','paid'=>'paid','success'=>'paid','successful'=>'paid','verified'=>'paid','done'=>'paid','completed'=>'paid','ok'=>'paid','100'=>'paid','101'=>'paid',
            'تایید'=>'paid','تاییدشد'=>'paid','تائید'=>'paid','تائیدشد'=>'paid','پرداختشد'=>'paid','موفق'=>'paid','پرداختموفق'=>'paid','تکمیلشد'=>'paid','واریزشد'=>'paid','شارژشد'=>'paid',
            'pending'=>'pending','created'=>'created','wait'=>'pending','waiting'=>'pending','درانتظار'=>'pending','درانتظاربررسی'=>'pending','ایجادشده'=>'created',
            'rejected'=>'rejected','reject'=>'rejected','cancelled'=>'cancelled','canceled'=>'cancelled','failed'=>'failed','error'=>'failed','ردشد'=>'rejected','لغوشد'=>'cancelled','ناموفق'=>'failed'
        ];
        return $map[$low] ?? ($low !== '' ? $low : 'paid');
    }
}

if(!function_exists('bot_payment_report_is_paid')){
    function bot_payment_report_is_paid($status){ return bot_payment_report_status_key($status) === 'paid'; }
}

if(!function_exists('bot_payment_report_status_title')){
    function bot_payment_report_status_title($status){
        $k = bot_payment_report_status_key($status);
        if($k === 'paid') return 'پرداخت موفق';
        if($k === 'pending') return 'در انتظار بررسی';
        if($k === 'created') return 'ایجاد شده';
        if($k === 'rejected') return 'رد شده';
        if($k === 'cancelled') return 'لغو شده';
        if($k === 'failed') return 'ناموفق';
        return (string)$status !== '' ? (string)$status : '-';
    }
}

if(!function_exists('bot_payment_report_source_title')){
    function bot_payment_report_source_title($source){
        $s = (string)$source;
        if($s === 'card') return 'کارت به کارت';
        if($s === 'zibal') return 'زیبال';
        if($s === 'zarinpal') return 'زرین‌پال قدیمی';
        if($s === 'legacy_gateway') return 'درگاه قدیمی';
        if($s === 'bot_payments') return 'جدول پرداخت‌ها';
        if($s === 'wallet_ledger') return 'دفتر موجودی';
        return $s !== '' ? $s : '-';
    }
}

if(!function_exists('bot_payment_report_row')){
    function bot_payment_report_row(array $r){
        $source = (string)($r['source'] ?? '');
        $status = (string)($r['status'] ?? 'paid');
        $paidAt = bot_payment_report_normalize_ts($r['paid_at'] ?? 0);
        $createdAt = bot_payment_report_normalize_ts($r['created_at'] ?? 0);
        $updatedAt = bot_payment_report_normalize_ts($r['updated_at'] ?? 0);
        $id = (string)($r['id'] ?? '');
        $fields = isset($r['fields']) && is_array($r['fields']) ? $r['fields'] : [];
        $row = [
            'source'=>$source,
            'source_title'=>bot_payment_report_source_title($source),
            'id'=>$id,
            'user_id'=>(string)($r['user_id'] ?? ''),
            'amount'=>bot_payment_report_amount($r['amount'] ?? 0),
            'reward'=>bot_payment_report_amount($r['reward'] ?? 0),
            'item'=>(string)($r['item'] ?? ''),
            'status'=>bot_payment_report_status_key($status),
            'status_title'=>bot_payment_report_status_title($status),
            'ref'=>(string)($r['ref'] ?? ''),
            'created_at'=>$createdAt,
            'updated_at'=>$updatedAt,
            'paid_at'=>$paidAt,
            'raw'=>$r['raw'] ?? [],
            'fields'=>$fields,
        ];
        $row['sort_ts'] = $paidAt > 0 ? $paidAt : ($updatedAt > 0 ? $updatedAt : $createdAt);
        $rawHash = substr(md5(json_encode($r['raw'] ?? $r, JSON_UNESCAPED_UNICODE)), 0, 12);
        $row['raw_hash'] = $rawHash;
        $row['unique_key'] = $source.':'.$id.':'.$row['ref'].':'.$row['user_id'].':'.$row['amount'].':'.$row['reward'].':'.$rawHash;
        return $row;
    }
}

if(!function_exists('bot_payment_report_read_json_sources')){
    function bot_payment_report_read_json_sources($relative, $default=[]){
        $relative = ltrim(str_replace('\\','/', (string)$relative), '/');
        $sources = [];
        $seen = [];
        if(function_exists('bot_data_get_json')){
            $v = bot_data_get_json($relative, null);
            if(is_array($v)){
                $key = md5(json_encode($v, JSON_UNESCAPED_UNICODE));
                $seen[$key] = true;
                $sources[] = ['name'=>'SQLJSON/managed: '.$relative, 'data'=>$v];
            }
        }
        foreach(bot_payment_report_roots() as $root){
            $paths = [$root.'/'.$relative];
            if(strpos($relative, 'app/') !== 0) $paths[] = $root.'/app/'.$relative;
            foreach($paths as $path){
                $path = str_replace('//','/',$path);
                if(!is_file($path)) continue;
                $raw = (string)@file_get_contents($path);
                $j = json_decode($raw, true);
                if(!is_array($j)) continue;
                $key = md5(json_encode($j, JSON_UNESCAPED_UNICODE));
                if(isset($seen[$key])) continue;
                $seen[$key] = true;
                $sources[] = ['name'=>'file: '.$path, 'data'=>$j];
            }
        }
        if(!$sources && is_array($default)) $sources[] = ['name'=>'empty: '.$relative, 'data'=>$default];
        return $sources;
    }
}

if(!function_exists('bot_payment_report_flat_records')){
    function bot_payment_report_flat_records($data, $preferredKeys=[]){
        $records = [];
        if(!is_array($data)) return $records;
        foreach($preferredKeys as $k){
            if(isset($data[$k]) && is_array($data[$k])){
                $data = $data[$k];
                break;
            }
        }
        foreach($data as $id=>$r){
            if(is_array($r)) $records[(string)$id] = $r;
        }
        return $records;
    }
}

if(!function_exists('bot_payment_report_collect_bot_payments')){
    function bot_payment_report_collect_bot_payments($includeAll=false){
        $rows = [];
        if(function_exists('bot_sql_pdo') && ($pdo = bot_sql_pdo())){
            try{
                if(bot_payment_report_sql_table_exists($pdo, 'bot_payments')){
                    $st = $pdo->query("SELECT * FROM bot_payments ORDER BY updated_at DESC, id DESC");
                    foreach($st->fetchAll(PDO::FETCH_ASSOC) as $r){
                        $status = (string)($r['status'] ?? '');
                        if(!$includeAll && !bot_payment_report_is_paid($status)) continue;
                        $data = bot_payment_report_json_data($r['data_json'] ?? []);
                        $source = trim((string)($data['source'] ?? ''));
                        if($source === '') $source = trim((string)($r['authority'] ?? '')) !== '' ? 'zibal' : 'bot_payments';
                        $item = (string)bot_payment_report_first($data, ['description','item_label','package_label','label','title'], '');
                        if($item === '' && !empty($r['panel_type'])) $item = 'پنل '.$r['panel_type'].' '.$r['panel_days'].' روزه';
                        $rows[] = bot_payment_report_row([
                            'source'=>$source === 'zibal' ? 'zibal' : 'bot_payments',
                            'id'=>(string)($r['id'] ?? ''),
                            'user_id'=>(string)($r['user_id'] ?? ''),
                            'amount'=>$r['amount'] ?? 0,
                            'reward'=>$r['reward_amount'] ?? 0,
                            'item'=>$item,
                            'status'=>$status,
                            'ref'=>(string)(bot_payment_report_first($r, ['ref_id','authority'], '') ?: bot_payment_report_first($data, ['ref_id','tracking_code','factor'], '')),
                            'created_at'=>$r['created_at'] ?? 0,
                            'updated_at'=>$r['updated_at'] ?? 0,
                            'paid_at'=>bot_payment_report_first($data, ['paid_at','approved_at','reviewed_at'], $r['updated_at'] ?? 0),
                            'fields'=>[
                                'شناسه پرداخت'=>$r['id'] ?? '', 'Authority'=>$r['authority'] ?? '', 'Ref ID'=>$r['ref_id'] ?? '', 'آیدی کاربر'=>$r['user_id'] ?? '',
                                'نوع'=>$r['type'] ?? '', 'مبلغ'=>$r['amount'] ?? '', 'الماس'=>$r['reward_amount'] ?? '', 'پنل'=>$r['panel_type'] ?? '', 'روز پنل'=>$r['panel_days'] ?? '',
                                'وضعیت'=>bot_payment_report_status_title($status), 'زمان ثبت'=>bot_payment_report_date(bot_payment_report_normalize_ts($r['created_at'] ?? 0)), 'زمان بروزرسانی'=>bot_payment_report_date(bot_payment_report_normalize_ts($r['updated_at'] ?? 0)), 'اطلاعات تکمیلی'=>$data
                            ],
                            'raw'=>$r,
                        ]);
                    }
                    bot_payment_report_source_add('SQL table: bot_payments', count($rows));
                }
            }catch(Throwable $e){ bot_payment_report_source_add('SQL table: bot_payments', 0, $e->getMessage()); }
        }
        return $rows;
    }
}

if(!function_exists('bot_payment_report_collect_manual')){
    function bot_payment_report_collect_manual($includeAll=false){
        $rows = [];
        $before = 0;
        if(function_exists('bot_sql_pdo') && ($pdo = bot_sql_pdo())){
            try{
                if(bot_payment_report_sql_table_exists($pdo, 'bot_manual_receipts')){
                    $st = $pdo->query("SELECT * FROM bot_manual_receipts ORDER BY updated_at DESC, id DESC");
                    foreach($st->fetchAll(PDO::FETCH_ASSOC) as $r){
                        $status = (string)($r['status'] ?? '');
                        if(!$includeAll && !bot_payment_report_is_paid($status)) continue;
                        $data = bot_payment_report_json_data($r['data_json'] ?? []);
                        $ts = bot_payment_report_first($data, ['approved_at','reviewed_at','paid_at'], $r['updated_at'] ?? $r['created_at'] ?? 0);
                        $ref = (string)bot_payment_report_first($data, ['tracking_code','ref_id','ref','receipt_code','card_trace','trace'], $r['admin_id'] ?? '');
                        $rows[] = bot_payment_report_row([
                            'source'=>'card', 'id'=>(string)($r['id'] ?? ''), 'user_id'=>(string)($r['user_id'] ?? ''), 'amount'=>$r['amount'] ?? 0, 'reward'=>$r['reward_amount'] ?? 0,
                            'item'=>(string)($r['package_label'] ?? ''), 'status'=>$status, 'ref'=>$ref, 'created_at'=>$r['created_at'] ?? 0, 'updated_at'=>$r['updated_at'] ?? 0, 'paid_at'=>$ts,
                            'fields'=>['شناسه رسید'=>$r['id'] ?? '', 'آیدی کاربر'=>$r['user_id'] ?? '', 'شماره بسته'=>$r['package_index'] ?? '', 'عنوان بسته'=>$r['package_label'] ?? '', 'مبلغ'=>$r['amount'] ?? '', 'الماس'=>$r['reward_amount'] ?? '', 'نوع رسید'=>$r['receipt_type'] ?? '', 'مقدار/فایل رسید'=>$r['receipt_value'] ?? '', 'وضعیت'=>bot_payment_report_status_title($status), 'ادمین بررسی کننده'=>$r['admin_id'] ?? '', 'کد پیگیری'=>$ref, 'زمان ثبت'=>bot_payment_report_date(bot_payment_report_normalize_ts($r['created_at'] ?? 0)), 'زمان بررسی'=>bot_payment_report_date(bot_payment_report_normalize_ts($ts)), 'اطلاعات تکمیلی'=>$data],
                            'raw'=>$r,
                        ]);
                    }
                    bot_payment_report_source_add('SQL table: bot_manual_receipts', count($rows));
                }
            }catch(Throwable $e){ bot_payment_report_source_add('SQL table: bot_manual_receipts', 0, $e->getMessage()); }
        }
        $before = count($rows);
        foreach(['cache/manual_receipts.json','app/cache/manual_receipts.json'] as $file){
            foreach(bot_payment_report_read_json_sources($file, []) as $src){
                $n = 0;
                foreach(bot_payment_report_flat_records($src['data'], ['receipts','items','data']) as $id=>$r){
                    $status = (string)bot_payment_report_first($r, ['status','state'], 'approved');
                    if(!$includeAll && !bot_payment_report_is_paid($status)) continue;
                    $data = bot_payment_report_json_data($r['data_json'] ?? ($r['data'] ?? []));
                    $ts = bot_payment_report_first($data, ['approved_at','reviewed_at','paid_at'], bot_payment_report_first($r, ['updated_at','created_at','time','date'], 0));
                    $ref = (string)bot_payment_report_first($data, ['tracking_code','ref_id','ref','receipt_code','card_trace','trace'], bot_payment_report_first($r, ['tracking_code','ref_id','ref','receipt_code','admin_id'], ''));
                    $rows[] = bot_payment_report_row([
                        'source'=>'card', 'id'=>(string)bot_payment_report_first($r, ['id','receipt_id'], $id), 'user_id'=>(string)bot_payment_report_first($r, ['user_id','fromid','from_id','chat_id'], ''),
                        'amount'=>bot_payment_report_first($r, ['amount','price','pay_amount','mablagh'], 0), 'reward'=>bot_payment_report_first($r, ['reward_amount','coin','coins','diamond','almas'], 0),
                        'item'=>(string)bot_payment_report_first($r, ['package_label','label','title','description','item'], ''), 'status'=>$status, 'ref'=>$ref,
                        'created_at'=>bot_payment_report_first($r, ['created_at','time','date'], 0), 'updated_at'=>bot_payment_report_first($r, ['updated_at','reviewed_at'], 0), 'paid_at'=>$ts,
                        'fields'=>['شناسه رسید'=>bot_payment_report_first($r, ['id','receipt_id'], $id), 'آیدی کاربر'=>bot_payment_report_first($r, ['user_id','fromid','from_id','chat_id'], ''), 'عنوان بسته'=>bot_payment_report_first($r, ['package_label','label','title','description','item'], ''), 'مبلغ'=>bot_payment_report_first($r, ['amount','price','pay_amount','mablagh'], 0), 'الماس'=>bot_payment_report_first($r, ['reward_amount','coin','coins','diamond','almas'], 0), 'نوع رسید'=>bot_payment_report_first($r, ['receipt_type','type'], ''), 'مقدار/فایل رسید'=>bot_payment_report_first($r, ['receipt_value','file_id','photo','document'], ''), 'وضعیت'=>bot_payment_report_status_title($status), 'کد پیگیری'=>$ref, 'منبع'=>$src['name'], 'اطلاعات کامل'=>$r],
                        'raw'=>$r,
                    ]);
                    $n++;
                }
                bot_payment_report_source_add($src['name'], $n);
            }
        }
        return $rows;
    }
}

if(!function_exists('bot_payment_report_collect_zibal')){
    function bot_payment_report_collect_zibal($includeAll=false){
        $rows = [];
        foreach(['cache/zibal_payments.json','app/cache/zibal_payments.json','cache/zarinpal_payments.json','app/cache/zarinpal_payments.json'] as $file){
            foreach(bot_payment_report_read_json_sources($file, []) as $src){
                $n = 0;
                foreach(bot_payment_report_flat_records($src['data'], ['payments','items','data']) as $authority=>$p){
                    $status = (string)bot_payment_report_first($p, ['status','state'], 'paid');
                    if(!$includeAll && !bot_payment_report_is_paid($status)) continue;
                    $verify = isset($p['verify']) && is_array($p['verify']) ? $p['verify'] : bot_payment_report_json_data($p['verify'] ?? []);
                    $ref = (string)bot_payment_report_first($verify, ['ref_id','refId','reference'], bot_payment_report_first($p, ['ref_id','ref','tracking_code'], ''));
                    $rows[] = bot_payment_report_row([
                        'source'=>(strpos((string)($src['path'] ?? ''),'zarinpal')!==false?'zarinpal':'zibal'), 'id'=>(string)($authority ?: bot_payment_report_first($p, ['authority','id'], '')), 'user_id'=>(string)bot_payment_report_first($p, ['user_id','fromid','from_id','chat_id'], ''),
                        'amount'=>bot_payment_report_first($p, ['amount','price','pay_amount'], 0), 'reward'=>bot_payment_report_first($p, ['reward_amount','coin','coins','diamond','almas'], 0), 'item'=>(string)bot_payment_report_first($p, ['description','type','item','label'], ''),
                        'status'=>$status, 'ref'=>$ref, 'created_at'=>bot_payment_report_first($p, ['created_at','time','date'], 0), 'updated_at'=>bot_payment_report_first($p, ['updated_at'], 0), 'paid_at'=>bot_payment_report_first($p, ['paid_at','verified_at'], 0),
                        'fields'=>['Authority'=>$authority ?: bot_payment_report_first($p, ['authority','id'], ''), 'آیدی کاربر'=>bot_payment_report_first($p, ['user_id','fromid','from_id','chat_id'], ''), 'مبلغ'=>bot_payment_report_first($p, ['amount','price','pay_amount'], 0), 'الماس'=>bot_payment_report_first($p, ['reward_amount','coin','coins','diamond','almas'], 0), 'شرح'=>bot_payment_report_first($p, ['description','item','label'], ''), 'نوع'=>bot_payment_report_first($p, ['type'], ''), 'وضعیت'=>bot_payment_report_status_title($status), 'کد پیگیری زیبال'=>$ref, 'اطلاعات verify'=>$verify, 'منبع'=>$src['name'], 'اطلاعات کامل'=>$p],
                        'raw'=>$p,
                    ]);
                    $n++;
                }
                bot_payment_report_source_add($src['name'], $n);
            }
        }
        return $rows;
    }
}

if(!function_exists('bot_payment_report_collect_legacy_factors')){
    function bot_payment_report_collect_legacy_factors($includeAll=false){
        $rows = [];
        foreach(['shop-factor.json','app/shop-factor.json'] as $file){
            foreach(bot_payment_report_read_json_sources($file, []) as $src){
                $factors = bot_payment_report_flat_records($src['data'], ['factor','factors','payments','items','data']);
                $n = 0;
                foreach($factors as $factorId=>$r){
                    $status = (string)bot_payment_report_first($r, ['status','state'], 'paid');
                    if(!$includeAll && !bot_payment_report_is_paid($status)) continue;
                    $amount = bot_payment_report_first($r, ['amount','price','pay_amount','mablagh'], 0);
                    $reward = bot_payment_report_first($r, ['coin','coins','reward_amount','diamond','almas'], 0);
                    $panel = (string)bot_payment_report_first($r, ['panel','panel_type'], '');
                    $type = (string)bot_payment_report_first($r, ['type','kind'], '');
                    $item = (string)bot_payment_report_first($r, ['item','description','label','title'], '');
                    if($item === '') $item = $panel !== '' ? ('پنل '.$panel) : (bot_payment_report_amount($reward) > 0 ? ($reward.' الماس') : $type);
                    $legacyTime = (string)bot_payment_report_first($r, ['time','date','created_at','paid_at'], '');
                    $rows[] = bot_payment_report_row([
                        'source'=>'legacy_gateway', 'id'=>(string)bot_payment_report_first($r, ['factor','factor_id','id'], $factorId), 'user_id'=>(string)bot_payment_report_first($r, ['fromid','user_id','from_id','chat_id'], ''),
                        'amount'=>$amount, 'reward'=>$reward, 'item'=>$item, 'status'=>$status, 'ref'=>(string)bot_payment_report_first($r, ['ref','ref_id','tracking_code'], $factorId),
                        'created_at'=>bot_payment_report_first($r, ['created_at'], 0), 'updated_at'=>bot_payment_report_first($r, ['updated_at'], 0), 'paid_at'=>bot_payment_report_first($r, ['paid_at'], 0),
                        'fields'=>['شماره فاکتور'=>bot_payment_report_first($r, ['factor','factor_id','id'], $factorId), 'آیدی کاربر'=>bot_payment_report_first($r, ['fromid','user_id','from_id','chat_id'], ''), 'مبلغ'=>$amount, 'الماس'=>$reward, 'پنل'=>$panel, 'نوع'=>$type, 'زمان ثبت قدیمی'=>$legacyTime, 'وضعیت'=>bot_payment_report_status_title($status), 'منبع'=>$src['name'], 'اطلاعات کامل'=>$r],
                        'raw'=>$r,
                    ]);
                    $n++;
                }
                bot_payment_report_source_add($src['name'], $n);
            }
        }
        return $rows;
    }
}

if(!function_exists('bot_payment_report_collect_wallet_ledger')){
    function bot_payment_report_collect_wallet_ledger($includeAll=false){
        $rows = [];
        if(function_exists('bot_sql_pdo') && ($pdo = bot_sql_pdo())){
            try{
                if(bot_payment_report_sql_table_exists($pdo, 'bot_wallet_ledger')){
                    $st = $pdo->query("SELECT * FROM bot_wallet_ledger WHERE delta_amount > 0 AND reason IN ('manual_card_shop','zibal_shop','zarinpal_shop') ORDER BY created_at DESC, id DESC");
                    foreach($st->fetchAll(PDO::FETCH_ASSOC) as $r){
                        $data = bot_payment_report_json_data($r['data_json'] ?? []);
                        $reason = (string)($r['reason'] ?? '');
                        $src = ($reason === 'zibal_shop' || $reason === 'zarinpal_shop') ? 'zibal' : 'card';
                        $ref = (string)bot_payment_report_first($data, ['authority','ref_id','receipt_id','tracking_code'], $r['ref_id'] ?? '');
                        $rows[] = bot_payment_report_row([
                            'source'=>'wallet_ledger', 'id'=>'ledger-'.$r['id'], 'user_id'=>$r['user_id'] ?? '', 'amount'=>bot_payment_report_first($data, ['amount','price'], 0), 'reward'=>$r['delta_amount'] ?? 0, 'item'=>$reason, 'status'=>'paid', 'ref'=>$ref,
                            'created_at'=>$r['created_at'] ?? 0, 'updated_at'=>$r['created_at'] ?? 0, 'paid_at'=>$r['created_at'] ?? 0,
                            'fields'=>['شناسه دفتر'=>$r['id'] ?? '', 'آیدی کاربر'=>$r['user_id'] ?? '', 'دلیل'=>$reason, 'تغییر موجودی'=>$r['delta_amount'] ?? '', 'موجودی قبل'=>$r['balance_before'] ?? '', 'موجودی بعد'=>$r['balance_after'] ?? '', 'کد مرجع'=>$ref, 'زمان'=>bot_payment_report_date(bot_payment_report_normalize_ts($r['created_at'] ?? 0)), 'اطلاعات تکمیلی'=>$data],
                            'raw'=>$r,
                        ]);
                    }
                    bot_payment_report_source_add('SQL table: bot_wallet_ledger payment rows', count($rows));
                }
            }catch(Throwable $e){ bot_payment_report_source_add('SQL table: bot_wallet_ledger payment rows', 0, $e->getMessage()); }
        }
        return $rows;
    }
}

if(!function_exists('bot_payment_report_source_is_shop_wallet_reason')){
    function bot_payment_report_source_is_shop_wallet_reason($reason, array $extra=[]){
        $r = strtolower(trim((string)$reason));
        if(in_array($r, ['manual_card_shop','zibal_shop','zarinpal_shop','shop_payment','shop_manual_payment','manualpay_approve','simple_shop'], true)) return true;
        if(isset($extra['receipt_id']) || isset($extra['payment_id']) || isset($extra['authority']) || isset($extra['ref_id'])){
            if(strpos($r, 'shop') !== false || strpos($r, 'pay') !== false || strpos($r, 'card') !== false || strpos($r, 'zarin') !== false) return true;
        }
        // Never count earned diamonds or admin gifts as income.
        foreach(['order_join_reward','daily_reward','referral','gift','admin_gift','left_return','mass_coin','bonus','mission','wheel'] as $bad){
            if(strpos($r, $bad) !== false) return false;
        }
        return false;
    }
}

if(!function_exists('bot_payment_report_find_package_amount_by_reward')){
    function bot_payment_report_find_package_amount_by_reward($reward){
        $reward = (int)$reward;
        if($reward <= 0) return 0;
        if(function_exists('bot_simple_shop_packages')){
            try{
                foreach((array)bot_simple_shop_packages() as $p){
                    if((int)($p['coins'] ?? 0) === $reward && (int)($p['amount'] ?? 0) > 0) return (int)$p['amount'];
                }
            }catch(Throwable $e){}
        }
        // Fallback: search common settings/json files for packages with coins+amount.
        foreach(['lib/kodam/simple_shop.json','app/lib/kodam/simple_shop.json','lib/kodam/shop_settings.json','app/lib/kodam/shop_settings.json'] as $rel){
            foreach(bot_payment_report_read_json_sources($rel, []) as $src){
                $stack = [$src['data']];
                while($stack){
                    $cur = array_pop($stack);
                    if(!is_array($cur)) continue;
                    $coins = bot_payment_report_deep_get_first($cur, ['coins','coin','reward_amount','reward','almas','diamond'], null);
                    $amount = bot_payment_report_deep_get_first($cur, ['amount','price','pay_amount','mablagh','rial'], null);
                    if($coins !== null && $amount !== null && (int)$coins === $reward && (int)$amount > 0) return (int)$amount;
                    foreach($cur as $v){ if(is_array($v)) $stack[] = $v; }
                }
            }
        }
        return 0;
    }
}

if(!function_exists('bot_payment_report_lookup_amount_by_ref')){
    function bot_payment_report_lookup_amount_by_ref($ref, $reward=0, $userId=''){
        $ref = trim((string)$ref); $reward=(int)$reward; $userId=trim((string)$userId);
        if($ref !== '' && function_exists('bot_sql_pdo') && ($pdo = bot_sql_pdo())){
            try{
                if(bot_payment_report_sql_table_exists($pdo, 'bot_manual_receipts')){
                    $st = $pdo->prepare('SELECT amount,reward_amount,package_label,status FROM bot_manual_receipts WHERE id=? OR receipt_value=? ORDER BY id DESC LIMIT 1');
                    $st->execute([$ref,$ref]);
                    $r = $st->fetch(PDO::FETCH_ASSOC);
                    if($r && (int)($r['amount'] ?? 0) > 0) return [(int)$r['amount'], (string)($r['package_label'] ?? ''), (string)($r['status'] ?? '')];
                }
                if(bot_payment_report_sql_table_exists($pdo, 'bot_payments')){
                    $st = $pdo->prepare('SELECT amount,reward_amount,panel_type,type,status FROM bot_payments WHERE id=? OR authority=? OR ref_id=? ORDER BY id DESC LIMIT 1');
                    $st->execute([$ref,$ref,$ref]);
                    $r = $st->fetch(PDO::FETCH_ASSOC);
                    if($r && (int)($r['amount'] ?? 0) > 0) return [(int)$r['amount'], trim((string)($r['type'] ?? '').' '.(string)($r['panel_type'] ?? '')), (string)($r['status'] ?? '')];
                }
            }catch(Throwable $e){}
        }
        foreach(['cache/manual_receipts.json','app/cache/manual_receipts.json','cache/zibal_payments.json','app/cache/zibal_payments.json'] as $rel){
            foreach(bot_payment_report_read_json_sources($rel, []) as $src){
                $records = bot_payment_report_flat_records($src['data'], ['receipts','payments','items','data']);
                foreach($records as $id=>$r){
                    if(!is_array($r)) continue;
                    $cand = [
                        (string)$id,
                        (string)bot_payment_report_deep_get_first($r, ['id','receipt_id','payment_id','authority','Authority','ref_id','tracking_code','receipt_value'], '')
                    ];
                    if($ref !== '' && !in_array($ref, $cand, true)) continue;
                    $amount = bot_payment_report_deep_get_first($r, ['amount','price','pay_amount','mablagh','rial'], 0);
                    if((int)$amount > 0) return [(int)$amount, (string)bot_payment_report_deep_get_first($r, ['package_label','label','title','description','item','type'], ''), (string)bot_payment_report_deep_get_first($r, ['status','state'], 'paid')];
                }
            }
        }
        $amount = bot_payment_report_find_package_amount_by_reward($reward);
        return [$amount, $amount > 0 ? 'تشخیص از بسته فروشگاه' : '', $amount > 0 ? 'paid' : ''];
    }
}

if(!function_exists('bot_payment_report_collect_wallet_logs_from_user_data')){
    function bot_payment_report_collect_wallet_logs_from_user_data($userId, array $data, $srcName, $includeAll=false){
        $rows=[];
        $logs = [];
        if(isset($data['wallet_log']) && is_array($data['wallet_log'])) $logs = $data['wallet_log'];
        elseif(isset($data['wallet']) && is_array($data['wallet']) && isset($data['wallet']['log']) && is_array($data['wallet']['log'])) $logs = $data['wallet']['log'];
        if(!$logs) return $rows;
        $i=0;
        foreach($logs as $log){
            $i++;
            if(!is_array($log)) continue;
            $delta = bot_payment_report_amount(bot_payment_report_first($log, ['delta','amount','reward','coins','diamond','almas'], 0));
            if($delta <= 0) continue;
            $reason = (string)bot_payment_report_first($log, ['reason','type','source'], '');
            if(!bot_payment_report_source_is_shop_wallet_reason($reason, $log)) continue;
            $ref = (string)bot_payment_report_first($log, ['receipt_id','payment_id','authority','ref_id','tracking_code','ref'], '');
            [$amount,$item,$lookupStatus] = bot_payment_report_lookup_amount_by_ref($ref, $delta, $userId);
            $source = (strpos(strtolower($reason.' '.$ref),'zarin') !== false || isset($log['authority'])) ? 'zibal' : 'card';
            $ts = bot_payment_report_first($log, ['time','created_at','date','paid_at'], 0);
            $rows[] = bot_payment_report_row([
                'source'=>$source,
                'id'=>'walletlog-'.$userId.'-'.$i,
                'user_id'=>(string)$userId,
                'amount'=>$amount,
                'reward'=>$delta,
                'item'=>$item !== '' ? $item : (string)$reason,
                'status'=>$lookupStatus !== '' ? $lookupStatus : 'paid',
                'ref'=>$ref !== '' ? $ref : ('walletlog-'.$i),
                'created_at'=>$ts,
                'updated_at'=>$ts,
                'paid_at'=>$ts,
                'fields'=>[
                    'منبع'=>'wallet_log کاربر',
                    'فایل/جدول'=>$srcName,
                    'آیدی کاربر'=>$userId,
                    'علت'=>$reason,
                    'مبلغ تشخیص داده شده'=>$amount,
                    'الماس شارژ شده'=>$delta,
                    'موجودی قبل'=>bot_payment_report_first($log, ['before','balance_before'], ''),
                    'موجودی بعد'=>bot_payment_report_first($log, ['after','balance_after'], ''),
                    'کد مرجع'=>$ref,
                    'اطلاعات کامل'=>$log,
                ],
                'raw'=>$log,
            ]);
        }
        return $rows;
    }
}

if(!function_exists('bot_payment_report_collect_user_wallet_logs')){
    function bot_payment_report_collect_user_wallet_logs($includeAll=false){
        $rows=[];
        if(function_exists('bot_sql_pdo') && ($pdo = bot_sql_pdo())){
            try{
                if(bot_payment_report_sql_table_exists($pdo, 'bot_users')){
                    $st = $pdo->query('SELECT user_id,data_json FROM bot_users ORDER BY updated_at DESC LIMIT 20000');
                    $before=count($rows);
                    foreach($st->fetchAll(PDO::FETCH_ASSOC) as $u){
                        $data = bot_payment_report_json_data($u['data_json'] ?? []);
                        $rows = array_merge($rows, bot_payment_report_collect_wallet_logs_from_user_data((string)($u['user_id'] ?? ''), $data, 'SQL table: bot_users.data_json', $includeAll));
                    }
                    bot_payment_report_source_add('SQL user wallet_log scan: bot_users', count($rows)-$before);
                }
            }catch(Throwable $e){ bot_payment_report_source_add('SQL user wallet_log scan: bot_users', 0, $e->getMessage()); }
        }
        foreach(bot_payment_report_roots() as $root){
            $files = glob($root.'/melat/*.json');
            if(!is_array($files)) continue;
            $before=count($rows); $scanned=0;
            foreach($files as $file){
                $scanned++;
                if($scanned > 30000) break;
                $raw = @file_get_contents($file);
                $data = is_string($raw) ? json_decode($raw, true) : null;
                if(!is_array($data)) continue;
                $uid = (string)bot_payment_report_first($data, ['user_id','id','chat_id'], pathinfo($file, PATHINFO_FILENAME));
                $rows = array_merge($rows, bot_payment_report_collect_wallet_logs_from_user_data($uid, $data, 'file: '.$file, $includeAll));
            }
            bot_payment_report_source_add('JSON user wallet_log scan: '.$root.'/melat/*.json', count($rows)-$before, 'files='.($scanned ?? 0));
        }
        return $rows;
    }
}



if(!function_exists('bot_payment_report_is_assoc')){
    function bot_payment_report_is_assoc($arr){ return is_array($arr) && array_keys($arr) !== range(0, count($arr)-1); }
}

if(!function_exists('bot_payment_report_deep_get_first')){
    function bot_payment_report_deep_get_first(array $arr, array $keys, $default=''){
        foreach($keys as $k){ if(array_key_exists($k, $arr) && $arr[$k] !== null && $arr[$k] !== '') return $arr[$k]; }
        // Also search one level deeper for common nested payment payloads.
        foreach(['data','data_json','payment','pay','receipt','verify','order','factor','invoice','meta','extra','details'] as $nk){
            if(isset($arr[$nk])){
                $sub = bot_payment_report_json_data($arr[$nk]);
                if(is_array($sub)){
                    foreach($keys as $k){ if(array_key_exists($k, $sub) && $sub[$k] !== null && $sub[$k] !== '') return $sub[$k]; }
                }
            }
        }
        return $default;
    }
}

if(!function_exists('bot_payment_report_looks_payment_like')){
    function bot_payment_report_looks_payment_like(array $r){
        $keys = array_change_key_case(array_flip(array_keys($r)), CASE_LOWER);
        $hasMoney = false; $hasUser = false; $hasRef = false; $hasStatus = false; $hasReward = false;
        foreach(['amount','price','pay_amount','mablagh','money','rial','rial_amount','total','total_amount','cost','fee','sum','value'] as $k){ if(isset($keys[$k])) { $hasMoney = true; break; } }
        foreach(['coin','coins','reward','reward_amount','diamond','diamonds','almas','point','points','delta_amount','balance_added'] as $k){ if(isset($keys[$k])) { $hasReward = true; break; } }
        foreach(['user_id','fromid','from_id','chat_id','uid','userid','telegram_id','member_id'] as $k){ if(isset($keys[$k])) { $hasUser = true; break; } }
        foreach(['authority','ref_id','refid','tracking_code','receipt_id','receipt_value','factor','factor_id','invoice_id','transaction_id','trace','card_trace'] as $k){ if(isset($keys[$k])) { $hasRef = true; break; } }
        foreach(['status','state','payment_status','verify_status','paid','approved','success'] as $k){ if(isset($keys[$k])) { $hasStatus = true; break; } }
        return ($hasMoney && ($hasUser || $hasRef || $hasReward || $hasStatus)) || ($hasReward && ($hasUser || $hasRef || $hasStatus));
    }
}

if(!function_exists('bot_payment_report_infer_source')){
    function bot_payment_report_infer_source(array $r, $srcName){
        $s = strtolower((string)$srcName.' '.json_encode(array_keys($r)));
        if(strpos($s,'zarin') !== false || isset($r['authority']) || isset($r['Authority'])) return 'zibal';
        if(strpos($s,'receipt') !== false || strpos($s,'card') !== false || isset($r['receipt_value']) || isset($r['receipt_type'])) return 'card';
        if(strpos($s,'wallet') !== false || isset($r['delta_amount'])) return 'wallet_ledger';
        if(strpos($s,'factor') !== false || strpos($s,'invoice') !== false) return 'legacy_gateway';
        return 'legacy_gateway';
    }
}

if(!function_exists('bot_payment_report_collect_generic_json_records')){
    function bot_payment_report_collect_generic_json_records($data, $srcName, $includeAll=false, $prefix='', &$rows=null, &$seen=null, $depth=0){
        if($rows === null) $rows = [];
        if($seen === null) $seen = [];
        if(!is_array($data) || $depth > 8 || count($rows) > 20000) return $rows;
        if(bot_payment_report_is_assoc($data) && bot_payment_report_looks_payment_like($data)){
            $rawKey = md5($srcName.'|'.$prefix.'|'.json_encode($data, JSON_UNESCAPED_UNICODE));
            if(!isset($seen[$rawKey])){
                $seen[$rawKey] = true;
                $status = (string)bot_payment_report_deep_get_first($data, ['status','state','payment_status','verify_status'], 'paid');
                if($includeAll || bot_payment_report_is_paid($status)){
                    $src = bot_payment_report_infer_source($data, $srcName);
                    $id = (string)bot_payment_report_deep_get_first($data, ['id','payment_id','receipt_id','factor','factor_id','invoice_id','transaction_id','authority','Authority'], $prefix);
                    $user = (string)bot_payment_report_deep_get_first($data, ['user_id','fromid','from_id','chat_id','uid','userid','telegram_id','member_id'], '');
                    $amount = bot_payment_report_deep_get_first($data, ['amount','price','pay_amount','mablagh','money','rial','rial_amount','total','total_amount','cost','fee','sum'], 0);
                    $reward = bot_payment_report_deep_get_first($data, ['reward_amount','coin','coins','reward','diamond','diamonds','almas','point','points','delta_amount','balance_added'], 0);
                    $ref = (string)bot_payment_report_deep_get_first($data, ['ref_id','refId','ref','tracking_code','receipt_code','receipt_id','authority','Authority','transaction_id','trace','card_trace'], $id);
                    $item = (string)bot_payment_report_deep_get_first($data, ['package_label','label','title','description','item','type','plan','panel','panel_type','kind'], '');
                    $created = bot_payment_report_deep_get_first($data, ['created_at','create_at','time','date','datetime','created','inserted_at'], 0);
                    $updated = bot_payment_report_deep_get_first($data, ['updated_at','reviewed_at','modified_at','verify_at'], 0);
                    $paid = bot_payment_report_deep_get_first($data, ['paid_at','approved_at','verified_at','reviewed_at','success_at'], 0);
                    $rows[] = bot_payment_report_row([
                        'source'=>$src,
                        'id'=>$id,
                        'user_id'=>$user,
                        'amount'=>$amount,
                        'reward'=>$reward,
                        'item'=>$item,
                        'status'=>$status,
                        'ref'=>$ref,
                        'created_at'=>$created,
                        'updated_at'=>$updated,
                        'paid_at'=>$paid,
                        'fields'=>[
                            'شناسه'=>$id,
                            'آیدی کاربر'=>$user,
                            'مبلغ'=>$amount,
                            'الماس'=>$reward,
                            'شرح/نوع'=>$item,
                            'وضعیت'=>bot_payment_report_status_title($status),
                            'کد پیگیری/مرجع'=>$ref,
                            'منبع'=>$srcName,
                            'مسیر رکورد'=>$prefix,
                            'اطلاعات کامل'=>$data,
                        ],
                        'raw'=>$data,
                    ]);
                }
            }
        }
        foreach($data as $k=>$v){
            if(is_array($v)) bot_payment_report_collect_generic_json_records($v, $srcName, $includeAll, $prefix === '' ? (string)$k : $prefix.'.'.$k, $rows, $seen, $depth+1);
        }
        return $rows;
    }
}

if(!function_exists('bot_payment_report_collect_generic_json_files')){
    function bot_payment_report_collect_generic_json_files($includeAll=false){
        $rows = [];
        $candidates = [];
        $rels = ['shop-factor.json','app/shop-factor.json','cache/manual_receipts.json','app/cache/manual_receipts.json','cache/zibal_payments.json','app/cache/zibal_payments.json'];
        foreach($rels as $rel){ foreach(bot_payment_report_read_json_sources($rel, []) as $src){ $candidates[] = $src; } }
        foreach(bot_payment_report_roots() as $root){
            foreach(['*.json','cache/*.json','app/*.json','app/cache/*.json','lib/kodam/*.json','app/lib/kodam/*.json'] as $pat){
                $files = glob($root.'/'.$pat);
                if(!is_array($files)) continue;
                foreach($files as $file){
                    $base = strtolower(basename($file));
                    if(!preg_match('/(pay|payment|receipt|factor|zarin|card|wallet|transaction|invoice|shop|manual)/i', $base)) continue;
                    $raw = @file_get_contents($file);
                    $j = is_string($raw) ? json_decode($raw, true) : null;
                    if(is_array($j)) $candidates[] = ['name'=>'generic file: '.$file, 'data'=>$j];
                }
            }
        }
        $seenSrc = [];
        foreach($candidates as $src){
            $sig = md5(($src['name'] ?? '').'|'.json_encode($src['data'] ?? [], JSON_UNESCAPED_UNICODE));
            if(isset($seenSrc[$sig])) continue;
            $seenSrc[$sig] = true;
            $before = count($rows);
            $rows = array_merge($rows, bot_payment_report_collect_generic_json_records($src['data'] ?? [], $src['name'] ?? 'json', $includeAll));
            bot_payment_report_source_add('generic scan: '.($src['name'] ?? 'json'), count($rows)-$before);
        }
        return $rows;
    }
}

if(!function_exists('bot_payment_report_collect_generic_sql_tables')){
    function bot_payment_report_collect_generic_sql_tables($includeAll=false){
        $rows = [];
        if(!function_exists('bot_sql_pdo') || !($pdo = bot_sql_pdo())) return $rows;
        try{
            $tables = $pdo->query('SHOW TABLES')->fetchAll(PDO::FETCH_COLUMN);
            foreach($tables as $table){
                $t = (string)$table;
                if(!preg_match('/(pay|payment|receipt|factor|wallet|transaction|invoice|order)/i', $t)) continue;
                if(in_array($t, ['bot_payments','bot_manual_receipts','bot_wallet_ledger'], true)) continue;
                $cols = $pdo->query('SHOW COLUMNS FROM `'.str_replace('`','``',$t).'`')->fetchAll(PDO::FETCH_ASSOC);
                $colNames = array_map(function($c){ return $c['Field'] ?? ''; }, $cols);
                $probe = array_flip(array_map('strtolower', $colNames));
                $hasCandidate = false;
                foreach(['amount','price','pay_amount','reward_amount','coin','coins','status','user_id','fromid','authority','ref_id','transaction_id'] as $c){ if(isset($probe[$c])) { $hasCandidate = true; break; } }
                if(!$hasCandidate) continue;
                $st = $pdo->query('SELECT * FROM `'.str_replace('`','``',$t).'` ORDER BY 1 DESC LIMIT 2000');
                $before = count($rows);
                foreach($st->fetchAll(PDO::FETCH_ASSOC) as $r){
                    $rows = array_merge($rows, bot_payment_report_collect_generic_json_records($r, 'SQL generic table: '.$t, $includeAll));
                }
                bot_payment_report_source_add('SQL generic table: '.$t, count($rows)-$before);
            }
        }catch(Throwable $e){ bot_payment_report_source_add('SQL generic scanner', 0, $e->getMessage()); }
        return $rows;
    }
}



// Event log collector: every approved payment is also written here from the shop handlers.
// This fixes empty reports on installs where old receipt/factor sources are not persisted in SQL/JSON.
if(!function_exists('bot_payment_report_event_log_paths')){
    function bot_payment_report_event_log_paths(){
        $paths = [];
        foreach(bot_payment_report_roots() as $root){
            $paths[] = rtrim($root,'/').'/cache/payment_report_events.json';
            $paths[] = rtrim($root,'/').'/app/cache/payment_report_events.json';
        }
        $out=[];
        foreach($paths as $p){ $p=str_replace('//','/',$p); if(!in_array($p,$out,true)) $out[]=$p; }
        return $out;
    }
}
if(!function_exists('bot_payment_report_collect_event_log')){
    function bot_payment_report_collect_event_log($includeAll=false){
        $rows=[];
        foreach(bot_payment_report_event_log_paths() as $path){
            if(!is_file($path)){ bot_payment_report_source_add('event log: '.$path, 0, 'missing'); continue; }
            $j=json_decode((string)@file_get_contents($path), true);
            if(!is_array($j)){ bot_payment_report_source_add('event log: '.$path, 0, 'bad_json'); continue; }
            $list = [];
            if(isset($j['events']) && is_array($j['events'])) $list=$j['events'];
            else $list=$j;
            $before=count($rows);
            foreach($list as $id=>$e){
                if(!is_array($e)) continue;
                $status=(string)bot_payment_report_first($e, ['status','state'], 'paid');
                if(!$includeAll && !bot_payment_report_is_paid($status)) continue;
                $source=(string)bot_payment_report_first($e, ['source','method','payment_method'], 'event_log');
                $amount=bot_payment_report_first($e, ['amount','price','pay_amount','rial','total_amount'], 0);
                $reward=bot_payment_report_first($e, ['reward_amount','reward','coin','coins','diamonds','almas','points'], 0);
                $ref=(string)bot_payment_report_first($e, ['ref','ref_id','receipt_id','authority','factor','factor_id','tracking_code'], (string)$id);
                $user=(string)bot_payment_report_first($e, ['user_id','fromid','from_id','chat_id','uid'], '');
                $item=(string)bot_payment_report_first($e, ['item','package_label','label','title','description','type','panel'], '');
                $rows[]=bot_payment_report_row([
                    'source'=>$source,
                    'id'=>(string)bot_payment_report_first($e, ['id','payment_id','receipt_id','factor','factor_id','authority'], (string)$id),
                    'user_id'=>$user,
                    'amount'=>$amount,
                    'reward'=>$reward,
                    'item'=>$item,
                    'status'=>$status,
                    'ref'=>$ref,
                    'created_at'=>bot_payment_report_first($e, ['created_at','created','time'], 0),
                    'updated_at'=>bot_payment_report_first($e, ['updated_at','reviewed_at'], 0),
                    'paid_at'=>bot_payment_report_first($e, ['paid_at','approved_at','verified_at'], time()),
                    'fields'=>[
                        'شناسه'=>(string)bot_payment_report_first($e, ['id','payment_id','receipt_id','factor','factor_id','authority'], (string)$id),
                        'آیدی کاربر'=>$user,
                        'روش پرداخت'=>bot_payment_report_source_title($source),
                        'مبلغ'=>$amount,
                        'الماس'=>$reward,
                        'شرح'=>$item,
                        'کد پیگیری/مرجع'=>$ref,
                        'وضعیت'=>bot_payment_report_status_title($status),
                        'ادمین تاییدکننده'=>bot_payment_report_first($e, ['admin_id','admin'], ''),
                        'منبع'=>'payment_report_events.json',
                        'اطلاعات کامل'=>$e,
                    ],
                    'raw'=>$e,
                ]);
            }
            bot_payment_report_source_add('event log: '.$path, count($rows)-$before, 'events='.(is_array($list)?count($list):0));
        }
        return $rows;
    }
}

if(!function_exists('bot_payment_report_collect')){
    function bot_payment_report_collect($fromTs=0, $toTs=0, $includeAll=false){
        bot_payment_report_sources_reset();
        $fromTs = (int)$fromTs; $toTs = (int)$toTs;
        $rows = array_merge(
            bot_payment_report_collect_bot_payments($includeAll),
            bot_payment_report_collect_manual($includeAll),
            bot_payment_report_collect_zibal($includeAll),
            bot_payment_report_collect_legacy_factors($includeAll),
            bot_payment_report_collect_wallet_ledger($includeAll),
            bot_payment_report_collect_user_wallet_logs($includeAll),
            bot_payment_report_collect_event_log($includeAll),
            bot_payment_report_collect_generic_json_files($includeAll),
            bot_payment_report_collect_generic_sql_tables($includeAll)
        );
        $dedup = [];
        $rows = array_values(array_filter($rows, function($r) use ($fromTs, $toTs, &$dedup){
            if(!is_array($r)) return false;
            $key = (string)($r['unique_key'] ?? md5(json_encode($r)));
            // Do not let wallet ledger duplicate a detailed manual/zibal row when same ref+user+reward exists.
            $loose = ($r['user_id'] ?? '').':'.($r['ref'] ?? '').':'.($r['reward'] ?? '').':'.($r['amount'] ?? '');
            $rawLoose = ($r['raw_hash'] ?? '').':'.($r['user_id'] ?? '').':'.($r['reward'] ?? '').':'.($r['amount'] ?? '');
            if(isset($dedup[$key]) || ($loose !== ':::' && isset($dedup['loose:'.$loose])) || ($rawLoose !== ':::' && isset($dedup['raw:'.$rawLoose]))) return false;
            $dedup[$key] = true;
            if(($r['source'] ?? '') !== 'wallet_ledger' && $loose !== ':::') $dedup['loose:'.$loose] = true;
            if(($r['source'] ?? '') !== 'wallet_ledger' && $rawLoose !== ':::') $dedup['raw:'.$rawLoose] = true;
            $ts = (int)($r['paid_at'] ?? 0); if($ts <= 0) $ts = (int)($r['updated_at'] ?? 0); if($ts <= 0) $ts = (int)($r['created_at'] ?? 0);
            if($ts <= 0 && ($fromTs > 0 || $toTs > 0)) return false;
            if($fromTs > 0 && $ts > 0 && $ts < $fromTs) return false;
            if($toTs > 0 && $ts > 0 && $ts > $toTs) return false;
            return true;
        }));
        usort($rows, function($a,$b){
            $ta = (int)($a['sort_ts'] ?? 0); $tb = (int)($b['sort_ts'] ?? 0);
            if($ta === $tb) return strcmp((string)($b['id'] ?? ''), (string)($a['id'] ?? ''));
            return $tb <=> $ta;
        });
        $totals = ['count'=>count($rows),'paid_count'=>0,'amount'=>0,'card_amount'=>0,'card_count'=>0,'zibal_amount'=>0,'zibal_count'=>0,'legacy_amount'=>0,'legacy_count'=>0,'reward'=>0,'daily'=>[],'methods'=>[]];
        foreach($rows as $r){
            if(!bot_payment_report_is_paid($r['status'] ?? '')) continue;
            $amount = (int)($r['amount'] ?? 0); $reward = (int)($r['reward'] ?? 0); $source = (string)($r['source'] ?? ''); $title = bot_payment_report_source_title($source);
            $totals['paid_count']++; $totals['amount'] += $amount; $totals['reward'] += $reward;
            if($source === 'card'){ $totals['card_amount'] += $amount; $totals['card_count']++; }
            if($source === 'zibal' || $source === 'zarinpal'){ $totals['zibal_amount'] += $amount; $totals['zibal_count']++; }
            if($source === 'legacy_gateway'){ $totals['legacy_amount'] += $amount; $totals['legacy_count']++; }
            if(!isset($totals['methods'][$title])) $totals['methods'][$title] = ['count'=>0,'amount'=>0];
            $totals['methods'][$title]['count']++; $totals['methods'][$title]['amount'] += $amount;
            $dayTs = (int)($r['paid_at'] ?: ($r['updated_at'] ?: $r['created_at']));
            $day = $dayTs > 0 ? date('Y-m-d', $dayTs) : 'گزارش قدیمی بدون تاریخ دقیق';
            if(!isset($totals['daily'][$day])) $totals['daily'][$day] = ['count'=>0,'amount'=>0];
            $totals['daily'][$day]['count']++; $totals['daily'][$day]['amount'] += $amount;
        }
        krsort($totals['daily']);
        return ['rows'=>$rows, 'totals'=>$totals, 'sources'=>bot_payment_report_sources_get(), 'from'=>$fromTs, 'to'=>$toTs, 'include_all'=>(bool)$includeAll, 'generated_at'=>time()];
    }
}

if(!function_exists('bot_payment_report_ascii')){
    function bot_payment_report_ascii($s, $maxLen=0){
        $s = (string)$s;
        $map = ['۰'=>'0','۱'=>'1','۲'=>'2','۳'=>'3','۴'=>'4','۵'=>'5','۶'=>'6','۷'=>'7','۸'=>'8','۹'=>'9','٠'=>'0','١'=>'1','٢'=>'2','٣'=>'3','٤'=>'4','٥'=>'5','٦'=>'6','٧'=>'7','٨'=>'8','٩'=>'9'];
        $s = strtr($s, $map);
        $s = preg_replace('/[^

\x20-\x7E]/u', '', $s);
        $s = preg_replace('/\s+/', ' ', $s);
        $s = trim($s);
        if($maxLen > 0 && strlen($s) > $maxLen) $s = substr($s, 0, max(0,$maxLen-3)).'...';
        return $s;
    }
}

if(!function_exists('bot_payment_pdf_escape')){
    function bot_payment_pdf_escape($s){
        $s = bot_payment_report_ascii($s);
        return str_replace(['\\','(',')'], ['\\\\','\\(','\\)'], $s);
    }
}


if(!function_exists('bot_payment_report_pdf_skip_field')){
    function bot_payment_report_pdf_skip_field($key){
        $key = trim((string)$key);
        $skip = ['اطلاعات کامل','اطلاعات verify','raw','data_json','metadata','meta','extra','details','verify','payment','receipt','order','factor'];
        foreach($skip as $s){
            if($key === $s) return true;
        }
        return false;
    }
}

if(!function_exists('bot_payment_report_lines_fa')){
    function bot_payment_report_lines_fa(array $report, $maxRows=300){
        $tot = $report['totals'] ?? [];
        $lines = [];
        $lines[] = ['type'=>'title','text'=>'گزارش کامل پرداخت‌ها و درآمد ربات'];
        $lines[] = ['type'=>'small','text'=>'زمان ساخت گزارش: '.bot_payment_report_fa_date($report['generated_at'] ?? time())];
        if(!empty($report['from']) || !empty($report['to'])){
            $lines[] = ['type'=>'small','text'=>'بازه گزارش: '.(!empty($report['from'])?bot_payment_report_fa_date($report['from']):'از ابتدا').' تا '.(!empty($report['to'])?bot_payment_report_fa_date($report['to']):'اکنون')];
        }
        $lines[] = ['type'=>'space','text'=>''];
        $lines[] = ['type'=>'h2','text'=>'خلاصه درآمد'];
        $lines[] = ['type'=>'text','text'=>'درآمد کل پرداخت‌های موفق: '.bot_payment_report_money($tot['amount'] ?? 0).' ریال'];
        $lines[] = ['type'=>'text','text'=>'تعداد پرداخت‌های موفق: '.number_format((int)($tot['paid_count'] ?? 0)).' از '.number_format((int)($tot['count'] ?? 0)).' رکورد'];
        $lines[] = ['type'=>'text','text'=>'کارت به کارت: '.bot_payment_report_money($tot['card_amount'] ?? 0).' ریال - '.number_format((int)($tot['card_count'] ?? 0)).' پرداخت'];
        $lines[] = ['type'=>'text','text'=>'زیبال: '.bot_payment_report_money($tot['zibal_amount'] ?? 0).' ریال - '.number_format((int)($tot['zibal_count'] ?? 0)).' پرداخت'];
        $lines[] = ['type'=>'text','text'=>'درگاه قدیمی/فاکتورهای قبلی: '.bot_payment_report_money($tot['legacy_amount'] ?? 0).' ریال - '.number_format((int)($tot['legacy_count'] ?? 0)).' پرداخت'];
        $lines[] = ['type'=>'text','text'=>'جمع الماس شارژ شده: '.bot_payment_report_money($tot['reward'] ?? 0).' الماس'];
        $lines[] = ['type'=>'space','text'=>''];
        $lines[] = ['type'=>'h2','text'=>'جمع روزانه'];
        if(empty($tot['daily'])) $lines[] = ['type'=>'text','text'=>'پرداخت موفقی پیدا نشد.'];
        foreach(($tot['daily'] ?? []) as $day=>$d){
            $lines[] = ['type'=>'text','text'=>$day.' - تعداد: '.number_format((int)$d['count']).' - مبلغ: '.bot_payment_report_money($d['amount']).' ریال'];
        }
        $lines[] = ['type'=>'space','text'=>''];
        $lines[] = ['type'=>'h2','text'=>'ریز مرتب پرداخت‌ها'];
        $i = 0;
        foreach(($report['rows'] ?? []) as $r){
            if($i++ >= $maxRows){ $lines[] = ['type'=>'small','text'=>'ادامه گزارش به دلیل حجم زیاد، در این PDF محدود شده است.']; break; }
            $ts = (int)($r['paid_at'] ?: ($r['updated_at'] ?: $r['created_at']));
            $lines[] = ['type'=>'sep','text'=>''];
            $lines[] = ['type'=>'h3','text'=>'پرداخت شماره '.number_format($i).' - '.$r['source_title'].' - '.$r['status_title']];
            $lines[] = ['type'=>'text','text'=>'کاربر: '.$r['user_id'].' | مبلغ: '.bot_payment_report_money($r['amount']).' ریال | الماس: '.bot_payment_report_money($r['reward'])];
            $lines[] = ['type'=>'text','text'=>'شناسه/فاکتور: '.$r['id'].' | کد پیگیری/Ref: '.($r['ref'] !== '' ? $r['ref'] : '-')];
            $lines[] = ['type'=>'text','text'=>'بسته/شرح: '.($r['item'] !== '' ? $r['item'] : '-')];
            $lines[] = ['type'=>'text','text'=>'زمان پرداخت/بررسی: '.($ts > 0 ? bot_payment_report_fa_date($ts) : '-')];
            $fields = isset($r['fields']) && is_array($r['fields']) ? $r['fields'] : [];
            foreach($fields as $k=>$v){
                if(bot_payment_report_pdf_skip_field($k)) continue;
                $value = bot_payment_report_str($v);
                if($value === '') continue;
                $lines[] = ['type'=>'small','text'=>$k.': '.$value];
            }
        }
        return $lines;
    }
}

if(!function_exists('bot_payment_report_find_font')){
    function bot_payment_report_find_font($bold=false){
        $root = defined('BOT_ROOT_DIR') ? BOT_ROOT_DIR : getcwd();
        $candidates = [];
        if($bold){
            $candidates = [
                $root.'/assets/fonts/Vazirmatn-Bold.ttf',
                $root.'/assets/fonts/Vazir-Bold.ttf',
                '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',
                '/usr/share/fonts/truetype/freefont/FreeSansBold.ttf',
            ];
        }else{
            $candidates = [
                $root.'/assets/fonts/Vazirmatn-Regular.ttf',
                $root.'/assets/fonts/Vazir.ttf',
                $root.'/assets/fonts/Vazir-Regular.ttf',
                '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
                '/usr/share/fonts/truetype/freefont/FreeSans.ttf',
            ];
        }
        foreach($candidates as $f){ if(is_file($f)) return $f; }
        return '';
    }
}

if(!function_exists('bot_payment_report_utf8_chars')){
    function bot_payment_report_utf8_chars($s){
        if(function_exists('preg_split')){
            $a = preg_split('//u', (string)$s, -1, PREG_SPLIT_NO_EMPTY);
            return is_array($a) ? $a : str_split((string)$s);
        }
        return str_split((string)$s);
    }
}

if(!function_exists('bot_payment_report_arabic_shape_char')){
    function bot_payment_report_arabic_shape_char($ch, $prevJoin, $nextJoin){
        static $map = null;
        if($map === null){
            $map = [
                'ا'=>['ﺍ','ﺎ','ﺍ','ﺎ',false], 'آ'=>['ﺁ','ﺂ','ﺁ','ﺂ',false], 'أ'=>['ﺃ','ﺄ','ﺃ','ﺄ',false], 'إ'=>['ﺇ','ﺈ','ﺇ','ﺈ',false],
                'ب'=>['ﺏ','ﺐ','ﺑ','ﺒ',true], 'پ'=>['ﭖ','ﭗ','ﭘ','ﭙ',true], 'ت'=>['ﺕ','ﺖ','ﺗ','ﺘ',true], 'ث'=>['ﺙ','ﺚ','ﺛ','ﺜ',true],
                'ج'=>['ﺝ','ﺞ','ﺟ','ﺠ',true], 'چ'=>['ﭺ','ﭻ','ﭼ','ﭽ',true], 'ح'=>['ﺡ','ﺢ','ﺣ','ﺤ',true], 'خ'=>['ﺥ','ﺦ','ﺧ','ﺨ',true],
                'د'=>['ﺩ','ﺪ','ﺩ','ﺪ',false], 'ذ'=>['ﺫ','ﺬ','ﺫ','ﺬ',false], 'ر'=>['ﺭ','ﺮ','ﺭ','ﺮ',false], 'ز'=>['ﺯ','ﺰ','ﺯ','ﺰ',false], 'ژ'=>['ﮊ','ﮋ','ﮊ','ﮋ',false],
                'س'=>['ﺱ','ﺲ','ﺳ','ﺴ',true], 'ش'=>['ﺵ','ﺶ','ﺷ','ﺸ',true], 'ص'=>['ﺹ','ﺺ','ﺻ','ﺼ',true], 'ض'=>['ﺽ','ﺾ','ﺿ','ﻀ',true],
                'ط'=>['ﻁ','ﻂ','ﻃ','ﻄ',true], 'ظ'=>['ﻅ','ﻆ','ﻇ','ﻈ',true], 'ع'=>['ﻉ','ﻊ','ﻋ','ﻌ',true], 'غ'=>['ﻍ','ﻎ','ﻏ','ﻐ',true],
                'ف'=>['ﻑ','ﻒ','ﻓ','ﻔ',true], 'ق'=>['ﻕ','ﻖ','ﻗ','ﻘ',true], 'ک'=>['ﮎ','ﮏ','ﮐ','ﮑ',true], 'ك'=>['ﻙ','ﻚ','ﻛ','ﻜ',true],
                'گ'=>['ﮒ','ﮓ','ﮔ','ﮕ',true], 'ل'=>['ﻝ','ﻞ','ﻟ','ﻠ',true], 'م'=>['ﻡ','ﻢ','ﻣ','ﻤ',true], 'ن'=>['ﻥ','ﻦ','ﻧ','ﻨ',true],
                'و'=>['ﻭ','ﻮ','ﻭ','ﻮ',false], 'ه'=>['ﻩ','ﻪ','ﻫ','ﻬ',true], 'ة'=>['ﺓ','ﺔ','ﺓ','ﺔ',false], 'ی'=>['ﯼ','ﯽ','ﯾ','ﯿ',true], 'ي'=>['ﻱ','ﻲ','ﻳ','ﻴ',true], 'ئ'=>['ﺉ','ﺊ','ﺋ','ﺌ',true],
            ];
        }
        if(!isset($map[$ch])) return $ch;
        $forms = $map[$ch];
        if($prevJoin && $nextJoin) return $forms[3];
        if($prevJoin) return $forms[1];
        if($nextJoin) return $forms[2];
        return $forms[0];
    }
}

if(!function_exists('bot_payment_report_arabic_can_join_prev')){
    function bot_payment_report_arabic_can_join_prev($ch){
        static $join = null;
        if($join === null) $join = array_flip(bot_payment_report_utf8_chars('بپتثجچحخسشصضطظعغفقکكگلمنهیيئه'));
        return isset($join[$ch]);
    }
}

if(!function_exists('bot_payment_report_arabic_is_letter')){
    function bot_payment_report_arabic_is_letter($ch){
        static $letters = null;
        if($letters === null) $letters = array_flip(bot_payment_report_utf8_chars('اآأإبپتثجچحخدذرزژسشصضطظعغفقکكگلمنوهةیيئ'));
        return isset($letters[$ch]);
    }
}

if(!function_exists('bot_payment_report_visual_rtl')){
    function bot_payment_report_visual_rtl($s){
        $s = (string)$s;
        $tokens = [];
        $s = preg_replace_callback('/[A-Za-z0-9_\.\-:\/\\#@\(\),]+/u', function($m) use (&$tokens){
            $idx = count($tokens);
            $tokens[$idx] = $m[0];
            return html_entity_decode('&#'.(57344+$idx).';', ENT_NOQUOTES, 'UTF-8');
        }, $s);
        $chars = bot_payment_report_utf8_chars($s);
        $out = [];
        $n = count($chars);
        for($i=0;$i<$n;$i++){
            $ch = $chars[$i];
            if(bot_payment_report_arabic_is_letter($ch)){
                $prev = $i>0 ? $chars[$i-1] : '';
                $next = $i<$n-1 ? $chars[$i+1] : '';
                $prevJoin = bot_payment_report_arabic_is_letter($prev) && bot_payment_report_arabic_can_join_prev($prev);
                $nextJoin = bot_payment_report_arabic_is_letter($next) && bot_payment_report_arabic_can_join_prev($ch);
                $out[] = bot_payment_report_arabic_shape_char($ch, $prevJoin, $nextJoin);
            }else $out[] = $ch;
        }
        $out = array_reverse($out);
        $vis = implode('', $out);
        foreach($tokens as $idx=>$tok){
            $vis = str_replace(html_entity_decode('&#'.(57344+$idx).';', ENT_NOQUOTES, 'UTF-8'), $tok, $vis);
        }
        return $vis;
    }
}

if(!function_exists('bot_payment_report_wrap_text')){
    function bot_payment_report_wrap_text($text, $maxChars){
        $text = trim((string)$text);
        if($text === '') return [''];
        $parts = preg_split('/\s+/u', $text);
        $lines = [];
        $cur = '';
        foreach($parts as $p){
            $candidate = $cur === '' ? $p : $cur.' '.$p;
            if(mb_strlen($candidate, 'UTF-8') > $maxChars && $cur !== ''){
                $lines[] = $cur;
                $cur = $p;
            }else $cur = $candidate;
        }
        if($cur !== '') $lines[] = $cur;
        return $lines;
    }
}

if(!function_exists('bot_payment_report_pdf_from_jpegs')){
    function bot_payment_report_pdf_from_jpegs($pdfPath, array $jpgPaths, $pageW=595, $pageH=842){
        $pdf = "%PDF-1.4\n";
        $offsets = [0];
        $addObj = function($body) use (&$pdf,&$offsets){
            $id = count($offsets);
            $offsets[$id] = strlen($pdf);
            $pdf .= $id." 0 obj\n".$body."\nendobj\n";
            return $id;
        };
        $imageIds = [];
        foreach($jpgPaths as $jp){
            $data = (string)@file_get_contents($jp);
            if($data === '') continue;
            $info = @getimagesize($jp);
            $w = is_array($info) ? (int)$info[0] : 1240;
            $h = is_array($info) ? (int)$info[1] : 1754;
            $imageIds[] = $addObj("<< /Type /XObject /Subtype /Image /Width {$w} /Height {$h} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ".strlen($data)." >>\nstream\n".$data."\nendstream");
        }
        if(!$imageIds) return false;
        $contentIds = [];
        foreach($imageIds as $imgId){
            $content = "q\n{$pageW} 0 0 {$pageH} 0 0 cm\n/Im0 Do\nQ\n";
            $contentIds[] = $addObj('<< /Length '.strlen($content).' >>' . "\nstream\n".$content."endstream");
        }
        $pagesId = count($offsets) + count($contentIds) + 1;
        $pageIds = [];
        foreach($contentIds as $i=>$cid){
            $imgId = $imageIds[$i];
            $pageIds[] = $addObj('<< /Type /Page /Parent '.$pagesId.' 0 R /MediaBox [0 0 '.$pageW.' '.$pageH.'] /Resources << /XObject << /Im0 '.$imgId.' 0 R >> >> /Contents '.$cid.' 0 R >>');
        }
        $kids = implode(' ', array_map(function($id){ return $id.' 0 R'; }, $pageIds));
        $realPagesId = $addObj('<< /Type /Pages /Kids ['.$kids.'] /Count '.count($pageIds).' >>');
        $catalogId = $addObj('<< /Type /Catalog /Pages '.$realPagesId.' 0 R >>');
        $xref = strlen($pdf);
        $pdf .= "xref\n0 ".count($offsets)."\n0000000000 65535 f \n";
        for($i=1;$i<count($offsets);$i++) $pdf .= sprintf("%010d 00000 n \n", $offsets[$i]);
        $pdf .= "trailer\n<< /Size ".count($offsets)." /Root {$catalogId} 0 R >>\nstartxref\n{$xref}\n%%EOF";
        $dir = dirname($pdfPath); if(!is_dir($dir)) @mkdir($dir,0775,true);
        return @file_put_contents($pdfPath, $pdf, LOCK_EX) !== false;
    }
}

if(!function_exists('bot_payment_report_pdf_write_persian_image')){
    function bot_payment_report_pdf_write_persian_image($path, array $report){
        if(!function_exists('imagecreatetruecolor') || !function_exists('imagettftext')) return false;
        $font = bot_payment_report_find_font(false);
        $fontBold = bot_payment_report_find_font(true);
        if($font === '') return false;
        if($fontBold === '') $fontBold = $font;
        $lines = bot_payment_report_lines_fa($report, 500);
        $w = 1240; $h = 1754; $margin = 80; $xRight = $w - $margin; $y = 100;
        $pages = [];
        $dir = bot_payment_report_cache_dir().'/tmp_'.date('YmdHis').'_'.mt_rand(1000,9999);
        @mkdir($dir, 0775, true);
        $img = null; $pageNo = 0;
        $newPage = function() use (&$img,&$pageNo,$w,$h,$dir){
            if($img){ @imagejpeg($img, $dir.'/page-'.$pageNo.'.jpg', 88); @imagedestroy($img); }
            $pageNo++;
            $img = imagecreatetruecolor($w, $h);
            $white = imagecolorallocate($img, 255, 255, 255);
            imagefilledrectangle($img, 0, 0, $w, $h, $white);
        };
        $newPage();
        foreach($lines as $ln){
            $type = $ln['type'] ?? 'text';
            $text = (string)($ln['text'] ?? '');
            $size = 24; $fontUse = $font; $colorRgb = [40,40,40]; $lineH = 44; $maxChars = 86;
            if($type === 'title'){ $size = 34; $fontUse = $fontBold; $colorRgb = [20,60,110]; $lineH = 62; $maxChars = 52; }
            elseif($type === 'h2'){ $size = 28; $fontUse = $fontBold; $colorRgb = [30,80,140]; $lineH = 54; $maxChars = 60; }
            elseif($type === 'h3'){ $size = 24; $fontUse = $fontBold; $colorRgb = [40,40,40]; $lineH = 44; $maxChars = 68; }
            elseif($type === 'small'){ $size = 19; $fontUse = $font; $colorRgb = [80,80,80]; $lineH = 35; $maxChars = 92; }
            elseif($type === 'sep'){
                if($y + 28 > $h - 70){ $newPage(); $y = 100; }
                $gray = imagecolorallocate($img, 220,220,220);
                imageline($img, $margin, $y, $w-$margin, $y, $gray);
                $y += 28;
                continue;
            }elseif($type === 'space'){ $y += 18; continue; }
            $wrapped = bot_payment_report_wrap_text($text, $maxChars);
            foreach($wrapped as $line){
                if($y + $lineH > $h - 90){ $newPage(); $y = 100; }
                $col = imagecolorallocate($img, $colorRgb[0], $colorRgb[1], $colorRgb[2]);
                $visual = bot_payment_report_visual_rtl($line);
                $bbox = imagettfbbox($size, 0, $fontUse, $visual);
                $tw = abs($bbox[2] - $bbox[0]);
                imagettftext($img, $size, 0, max($margin, $xRight - $tw), $y, $col, $fontUse, $visual);
                $y += $lineH;
            }
        }
        if($img){ @imagejpeg($img, $dir.'/page-'.$pageNo.'.jpg', 88); @imagedestroy($img); }
        $jpgs = glob($dir.'/page-*.jpg');
        sort($jpgs, SORT_NATURAL);
        $ok = bot_payment_report_pdf_from_jpegs($path, $jpgs);
        foreach($jpgs as $jp) @unlink($jp);
        @rmdir($dir);
        return $ok;
    }
}

if(!function_exists('bot_payment_report_pdf_write_ascii')){
    function bot_payment_report_pdf_write_ascii($path, array $report){
        $lines = [];
        $tot = $report['totals'] ?? [];
        $lines[] = ['font'=>'B','size'=>18,'text'=>'Full Payment Income Report'];
        $lines[] = ['font'=>'R','size'=>10,'text'=>'Generated at: '.bot_payment_report_date($report['generated_at'] ?? time()).' Asia/Tehran'];
        $lines[] = ['font'=>'R','size'=>10,'text'=>'Note: Persian PDF renderer is unavailable on this host. Install PHP GD + a TTF font for Persian output.'];
        if(!empty($report['from']) || !empty($report['to'])){
            $lines[] = ['font'=>'R','size'=>10,'text'=>'Period: '.(!empty($report['from'])?bot_payment_report_date($report['from']):'start').' to '.(!empty($report['to'])?bot_payment_report_date($report['to']):'now')];
        }
        $lines[] = ['font'=>'R','size'=>10,'text'=>''];
        $lines[] = ['font'=>'B','size'=>12,'text'=>'Summary'];
        $lines[] = ['font'=>'R','size'=>10,'text'=>'Paid payments: '.(int)($tot['paid_count'] ?? 0).' / Total rows: '.(int)($tot['count'] ?? 0)];
        $lines[] = ['font'=>'R','size'=>10,'text'=>'Total income: '.bot_payment_report_money($tot['amount'] ?? 0).' Rial'];
        $lines[] = ['font'=>'R','size'=>10,'text'=>'Card: '.bot_payment_report_money($tot['card_amount'] ?? 0).' Rial / '.(int)($tot['card_count'] ?? 0).' rows'];
        $lines[] = ['font'=>'R','size'=>10,'text'=>'Zibal: '.bot_payment_report_money($tot['zibal_amount'] ?? 0).' Rial / '.(int)($tot['zibal_count'] ?? 0).' rows'];
        $lines[] = ['font'=>'R','size'=>10,'text'=>'Legacy factors: '.bot_payment_report_money($tot['legacy_amount'] ?? 0).' Rial / '.(int)($tot['legacy_count'] ?? 0).' rows'];
        $lines[] = ['font'=>'R','size'=>10,'text'=>'Diamonds charged: '.bot_payment_report_money($tot['reward'] ?? 0)];
        $lines[] = ['font'=>'R','size'=>10,'text'=>''];
        $lines[] = ['font'=>'B','size'=>12,'text'=>'Daily totals'];
        foreach(($tot['daily'] ?? []) as $day=>$d){
            $lines[] = ['font'=>'M','size'=>9,'text'=>bot_payment_report_ascii($day).' | count '.(int)$d['count'].' | amount '.bot_payment_report_money($d['amount']).' Rial'];
        }
        $lines[] = ['font'=>'R','size'=>10,'text'=>''];
        $lines[] = ['font'=>'B','size'=>12,'text'=>'Clean payment details'];
        $i = 0;
        foreach(($report['rows'] ?? []) as $r){
            if($i++ >= 500){ $lines[] = ['font'=>'M','size'=>8,'text'=>'... truncated to 500 rows']; break; }
            $ts = (int)($r['paid_at'] ?: ($r['updated_at'] ?: $r['created_at']));
            $lines[] = ['font'=>'M','size'=>8,'text'=>str_repeat('-', 90)];
            $lines[] = ['font'=>'B','size'=>9,'text'=>'Payment #'.$i.' | '.$r['source'].' | '.$r['status'].' | User '.$r['user_id']];
            $lines[] = ['font'=>'M','size'=>8,'text'=>'ID: '.bot_payment_report_ascii($r['id'],28).' | Ref: '.bot_payment_report_ascii($r['ref'],28).' | Amount: '.bot_payment_report_money($r['amount']).' | Diamonds: '.bot_payment_report_money($r['reward'])];
            $lines[] = ['font'=>'M','size'=>8,'text'=>'Item: '.bot_payment_report_ascii($r['item'],90)];
            $lines[] = ['font'=>'M','size'=>8,'text'=>'Date: '.($ts > 0 ? bot_payment_report_date($ts) : '-')];
            foreach(($r['fields'] ?? []) as $k=>$v){
                if(bot_payment_report_pdf_skip_field($k)) continue;
                $val = bot_payment_report_ascii(bot_payment_report_str($v), 78);
                if($val === '') continue;
                $lines[] = ['font'=>'M','size'=>8,'text'=>bot_payment_report_ascii($k,24).': '.$val];
            }
        }
        $objects = [];
        $pages = [];
        $pageLines = [];
        $y = 800;
        foreach($lines as $line){
            $size = (int)($line['size'] ?? 10);
            $height = max(11, $size + 4);
            if($y - $height < 55){ $pages[] = $pageLines; $pageLines = []; $y = 800; }
            $pageLines[] = $line;
            $y -= $height;
        }
        if($pageLines) $pages[] = $pageLines;
        if(!$pages) $pages[] = [];
        $pdf = "%PDF-1.4\n";
        $offsets = [0];
        $addObj = function($body) use (&$pdf,&$offsets){
            $id = count($offsets);
            $offsets[$id] = strlen($pdf);
            $pdf .= $id." 0 obj\n".$body."\nendobj\n";
            return $id;
        };
        $fontR = $addObj('<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>');
        $fontB = $addObj('<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold >>');
        $fontM = $addObj('<< /Type /Font /Subtype /Type1 /BaseFont /Courier >>');
        $pageIds = [];
        $contentIds = [];
        foreach($pages as $pi=>$plines){
            $content = "BT\n";
            $y = 800;
            foreach($plines as $ln){
                $fontKey = ($ln['font'] ?? 'R') === 'B' ? 'F2' : (($ln['font'] ?? 'R') === 'M' ? 'F3' : 'F1');
                $size = (int)($ln['size'] ?? 10);
                $height = max(11, $size + 4);
                $text = bot_payment_pdf_escape($ln['text'] ?? '');
                $content .= "/{$fontKey} {$size} Tf 1 0 0 1 45 {$y} Tm ({$text}) Tj\n";
                $y -= $height;
            }
            $content .= "/F1 8 Tf 1 0 0 1 45 30 Tm (Page ".($pi+1).' / '.count($pages).") Tj\n";
            $content .= "ET\n";
            $contentIds[] = $addObj('<< /Length '.strlen($content).' >>' . "\nstream\n".$content."endstream");
        }
        $pagesId = 4 + (2 * count($contentIds));
        foreach($contentIds as $cid){
            $pageIds[] = $addObj('<< /Type /Page /Parent '.$pagesId.' 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 '.$fontR.' 0 R /F2 '.$fontB.' 0 R /F3 '.$fontM.' 0 R >> >> /Contents '.$cid.' 0 R >>');
        }
        $kids = implode(' ', array_map(function($id){ return $id.' 0 R'; }, $pageIds));
        $realPagesId = $addObj('<< /Type /Pages /Kids ['.$kids.'] /Count '.count($pageIds).' >>');
        $catalogId = $addObj('<< /Type /Catalog /Pages '.$realPagesId.' 0 R >>');
        $xref = strlen($pdf);
        $pdf .= "xref\n0 ".count($offsets)."\n0000000000 65535 f \n";
        for($i=1;$i<count($offsets);$i++) $pdf .= sprintf("%010d 00000 n \n", $offsets[$i]);
        $pdf .= "trailer\n<< /Size ".count($offsets)." /Root {$catalogId} 0 R >>\nstartxref\n{$xref}\n%%EOF";
        $dir = dirname($path); if(!is_dir($dir)) @mkdir($dir,0775,true);
        return @file_put_contents($path, $pdf, LOCK_EX) !== false;
    }
}

if(!function_exists('bot_payment_report_pdf_write')){
    function bot_payment_report_pdf_write($path, array $report){
        $ok = bot_payment_report_pdf_write_persian_image($path, $report);
        if($ok) return true;
        return bot_payment_report_pdf_write_ascii($path, $report);
    }
}

if(!function_exists('bot_payment_report_generate_pdf')){
    function bot_payment_report_generate_pdf($fromTs=0, $toTs=0, $includeAll=false){
        $report = bot_payment_report_collect($fromTs, $toTs, $includeAll);
        $name = 'payment-full-report-'.date('Ymd-His').'.pdf';
        $path = bot_payment_report_cache_dir().'/'.$name;
        $ok = bot_payment_report_pdf_write($path, $report);
        $isPersian = function_exists('imagecreatetruecolor') && function_exists('imagettftext') && bot_payment_report_find_font(false) !== '';
        return ['ok'=>$ok, 'path'=>$path, 'name'=>$name, 'report'=>$report, 'persian_pdf'=>$isPersian];
    }
}

if(!function_exists('bot_payment_report_send_pdf_to_admin')){
    function bot_payment_report_send_pdf_to_admin($chatId, $fromTs=0, $toTs=0){
        $res = bot_payment_report_generate_pdf($fromTs, $toTs, false);
        if(empty($res['ok']) || empty($res['path']) || !is_file($res['path'])) return ['ok'=>false,'error'=>'pdf_generation_failed'];
        if(!function_exists('bot') || !function_exists('curl_init') || !class_exists('CURLFile', false)) return ['ok'=>false,'error'=>'telegram_upload_unavailable','path'=>$res['path']];
        $tot = $res['report']['totals'] ?? [];
        $caption = "📄 گزارش کامل PDF پرداخت‌ها\n\n".
                   "💰 درآمد کل: <b>".number_format((int)($tot['amount'] ?? 0))." ریال</b>\n".
                   "🧾 پرداخت موفق: <b>".(int)($tot['paid_count'] ?? 0)."</b>\n".
                   "📦 گزارش‌های قبلی/درگاه قدیمی: <b>".(int)($tot['legacy_count'] ?? 0)."</b>\n".
                   (empty($res['persian_pdf']) ? "\n⚠️ روی هاست، GD یا فونت فارسی پیدا نشد؛ PDF با حالت سازگار ساخته شد." : "");
        $send = bot('sendDocument', [
            'chat_id'=>(string)$chatId,
            'document'=>new CURLFile(realpath($res['path']), 'application/pdf', $res['name']),
            'caption'=>$caption,
            'parse_mode'=>'HTML',
        ]);
        return ['ok'=>is_object($send) && !empty($send->ok), 'response'=>$send, 'path'=>$res['path'], 'report'=>$res['report']];
    }
}

if(!function_exists('bot_payment_report_list_generated_pdfs')){
    function bot_payment_report_list_generated_pdfs($limit=20){
        $files = glob(bot_payment_report_cache_dir().'/*.pdf');
        if(!is_array($files)) return [];
        usort($files, function($a,$b){ return filemtime($b) <=> filemtime($a); });
        $out = [];
        foreach(array_slice($files, 0, (int)$limit) as $f){
            $out[] = ['name'=>basename($f),'path'=>$f,'size'=>filesize($f),'time'=>filemtime($f)];
        }
        return $out;
    }
}
