<?php
/** Reservation / booking system. Weekly recurring slots per game/day. */
function reservationDb(){static $pdo=null;if($pdo instanceof PDO)return $pdo;require_once __DIR__.'/db.php';$pdo=db();$pdo->exec("SET time_zone = '+03:30'");
    $pdo->exec("CREATE TABLE IF NOT EXISTS booking_slot_templates (id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, game_id INT NOT NULL, weekday TINYINT UNSIGNED NOT NULL DEFAULT 0, slot_time TIME NOT NULL, per_person_amount DECIMAL(12,2) NOT NULL DEFAULT 0, amount DECIMAL(12,2) NOT NULL DEFAULT 0, manual_closed TINYINT(1) NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uq_game_weekday_time(game_id,weekday,slot_time), KEY idx_template_game_day(game_id,weekday)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
    foreach(["ALTER TABLE booking_slot_templates ADD COLUMN weekday TINYINT UNSIGNED NOT NULL DEFAULT 0 AFTER game_id","ALTER TABLE booking_slot_templates ADD COLUMN per_person_amount DECIMAL(12,2) NOT NULL DEFAULT 0 AFTER slot_time","ALTER TABLE booking_slot_templates ADD COLUMN amount DECIMAL(12,2) NOT NULL DEFAULT 0 AFTER per_person_amount","ALTER TABLE booking_slot_templates ADD COLUMN manual_closed TINYINT(1) NOT NULL DEFAULT 0 AFTER amount"] as $sql){try{$pdo->exec($sql);}catch(Throwable $e){}}
    // مهاجرت امن ساختار سانس هفتگی: ایندکس قدیمی game+time باید حتماً حذف شود،
    // و قبل از ساخت ایندکس جدید، رکوردهای تکراری هفتگی پاک شوند.
    // مهاجرت قطعی ایندکس‌های قدیمی: هر UNIQUE قدیمی که weekday را در کلید ندارد
    // باید حذف شود؛ در غیر این صورت مثلاً 13:00 شنبه و یکشنبه با هم تداخل می‌کنند.
    // حذف قطعی ایندکس قدیمی که فقط game_id+slot_time بود.
    // این را مستقیم با نام هم انجام می‌دهیم چون بعضی نسخه‌های MySQL ممکن است
    // ترتیب/متادیتای INDEX را طوری برگردانند که تشخیص قبلی آن را نبیند.
    try{$pdo->exec("ALTER TABLE booking_slot_templates DROP INDEX uq_game_time");}catch(Throwable $e){}
    try{$pdo->exec("ALTER TABLE booking_slots DROP INDEX uq_game_time");}catch(Throwable $e){}
    try{
        $idx=$pdo->query("SELECT DISTINCT INDEX_NAME FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='booking_slot_templates' AND NON_UNIQUE=0 AND INDEX_NAME<>'PRIMARY'")->fetchAll(PDO::FETCH_COLUMN);
        foreach($idx as $name){
            $cols=$pdo->prepare("SELECT COLUMN_NAME FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='booking_slot_templates' AND INDEX_NAME=? ORDER BY SEQ_IN_INDEX");
            $cols->execute([$name]); $c=$cols->fetchAll(PDO::FETCH_COLUMN);
            if($name==='uq_game_time' || ($c===['game_id','slot_time'])){
                $safe=preg_replace('/[^A-Za-z0-9_]/','',$name);
                if($safe) $pdo->exec("ALTER TABLE booking_slot_templates DROP INDEX `{$safe}`");
            }
        }
    }catch(Throwable $e){}
    try{$pdo->exec("DELETE t1 FROM booking_slot_templates t1 INNER JOIN booking_slot_templates t2 ON t1.game_id=t2.game_id AND t1.weekday=t2.weekday AND t1.slot_time=t2.slot_time AND t1.id>t2.id");}catch(Throwable $e){}
    try{$pdo->exec("ALTER TABLE booking_slot_templates DROP INDEX uq_game_weekday_time");}catch(Throwable $e){}
    try{$pdo->exec("ALTER TABLE booking_slot_templates ADD UNIQUE KEY uq_game_weekday_time(game_id,weekday,slot_time)");}catch(Throwable $e){}
    $pdo->exec("CREATE TABLE IF NOT EXISTS booking_slots (id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, game_id INT NOT NULL, slot_date DATE NOT NULL, slot_time TIME NOT NULL, per_person_amount DECIMAL(12,2) NOT NULL DEFAULT 0, amount DECIMAL(12,2) NOT NULL DEFAULT 0, manual_closed TINYINT(1) NOT NULL DEFAULT 0, reserved TINYINT(1) NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uq_slot(game_id,slot_date,slot_time), KEY idx_game_date(game_id,slot_date)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
    foreach(["ALTER TABLE booking_slots ADD COLUMN slot_date DATE NULL AFTER game_id","ALTER TABLE booking_slots ADD COLUMN per_person_amount DECIMAL(12,2) NOT NULL DEFAULT 0 AFTER slot_time","ALTER TABLE booking_slots ADD COLUMN amount DECIMAL(12,2) NOT NULL DEFAULT 0 AFTER per_person_amount","ALTER TABLE booking_slots ADD COLUMN manual_closed TINYINT(1) NOT NULL DEFAULT 0 AFTER amount","ALTER TABLE booking_slots ADD COLUMN reserved TINYINT(1) NOT NULL DEFAULT 0 AFTER manual_closed"] as $sql){try{$pdo->exec($sql);}catch(Throwable $e){}}
    try{$pdo->exec("ALTER TABLE booking_slots DROP INDEX uq_slot");}catch(Throwable $e){} try{$pdo->exec("ALTER TABLE booking_slots ADD UNIQUE KEY uq_slot(game_id,slot_date,slot_time)");}catch(Throwable $e){} try{$pdo->exec("ALTER TABLE booking_slots ADD KEY idx_game_date(game_id,slot_date)");}catch(Throwable $e){}
    foreach(["ALTER TABLE reservations ADD COLUMN per_person_amount DECIMAL(12,2) NOT NULL DEFAULT 0 AFTER amount","ALTER TABLE reservations ADD COLUMN booking_code VARCHAR(20) NULL AFTER per_person_amount","ALTER TABLE reservations ADD COLUMN full_name VARCHAR(255) NOT NULL DEFAULT '' AFTER booking_code","ALTER TABLE reservations ADD COLUMN receipt_type VARCHAR(20) NULL AFTER receipt_file_id"] as $sql){try{$pdo->exec($sql);}catch(Throwable $e){}}
    return $pdo;
}
function reservationEnabled(){$f=__DIR__.'/data/reservation_enabled.json';if(!is_file($f))return false;$d=json_decode((string)@file_get_contents($f),true);return is_array($d)&&!empty($d['enabled']);}
function setReservationEnabled($enabled,$by){$f=__DIR__.'/data/reservation_enabled.json';return @file_put_contents($f,json_encode(['enabled'=>(bool)$enabled,'updated_at'=>date('Y-m-d H:i:s'),'updated_by'=>(int)$by],JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT),LOCK_EX)!==false;}
function reservationTz(){return new DateTimeZone('Asia/Tehran');} function reservationNow(){return new DateTime('now',reservationTz());} function reservationToday(){return reservationNow()->format('Y-m-d');}
function reservationDays($offset=0){$d=reservationNow();$d->modify(($offset>=0?'+':'').$offset.' day');return $d->format('Y-m-d');}
function reservationGregorianToJalali($gy,$gm,$gd){
    $gdm=[31,28,31,30,31,30,31,31,30,31,30,31];
    $gy2=$gy-1600; $jy=979;
    if($gy<=1600){$gy2=$gy-621;$jy=0;}
    $gy2Next=$gy2+($gm>2?1:0);
    $days=365*$gy2+intdiv($gy2Next+3,4)-intdiv($gy2Next+99,100)+intdiv($gy2Next+399,400)-80+$gd;
    for($i=1;$i<$gm;$i++)$days+=$gdm[$i-1];
    if($gm>2 && (($gy2%4===0 && $gy2%100!==0)||($gy2%400===0)))$days++;
    $jy+=33*intdiv($days,12053); $days%=12053;
    $jy+=4*intdiv($days,1461); $days%=1461;
    if($days>365){$jy+=intdiv($days-1,365);$days=($days-1)%365;}
    if($days<186){$jm=1+intdiv($days,31);$jd=1+($days%31);}
    else{$jm=7+intdiv($days-186,30);$jd=1+(($days-186)%30);}
    return[$jy,$jm,$jd];
}
function reservationPersianMonthName($month){$names=['فروردین','اردیبهشت','خرداد','تیر','مرداد','شهریور','مهر','آبان','آذر','دی','بهمن','اسفند'];return $names[(int)$month-1]??'';}
function reservationWeekdayIndexFromDate($date){$d=DateTime::createFromFormat('!Y-m-d',$date,reservationTz());if(!$d)return 0;return((int)$d->format('N')+1)%7;}
function reservationWeekdayName($weekday){$names=['شنبه','یکشنبه','دوشنبه','سه‌شنبه','چهارشنبه','پنجشنبه','جمعه'];return $names[(int)$weekday]??'شنبه';}
function reservationFaDigits($value){return strtr((string)$value,['0'=>'۰','1'=>'۱','2'=>'۲','3'=>'۳','4'=>'۴','5'=>'۵','6'=>'۶','7'=>'۷','8'=>'۸','9'=>'۹']);}
function reservationDayLabel($date){$d=DateTime::createFromFormat('!Y-m-d',$date,reservationTz());if(!$d)return$date;[$jy,$jm,$jd]=reservationGregorianToJalali((int)$d->format('Y'),(int)$d->format('n'),(int)$d->format('j'));return reservationWeekdayName(reservationWeekdayIndexFromDate($date)).' '.reservationFaDigits($jd).' '.reservationPersianMonthName($jm).' '.reservationFaDigits($jy);}
function reservationEnsureGame($gameId){$p=reservationDb();$s=$p->prepare('SELECT * FROM booking_game_settings WHERE game_id=?');$s->execute([(int)$gameId]);$r=$s->fetch();if(!$r){$s=$p->prepare("INSERT INTO booking_game_settings(game_id,receipt_routing,card_number,card_holder,amount) VALUES(?,'admins','','',0)");$s->execute([(int)$gameId]);return['game_id'=>(int)$gameId,'receipt_routing'=>'admins','card_number'=>'','card_holder'=>'','amount'=>0];}return$r;}
function reservationGameSettings($gameId){return reservationEnsureGame($gameId);} function reservationSaveGameSettings($gameId,$changes){reservationEnsureGame($gameId);$allowed=['receipt_routing','card_number','card_holder','amount'];$set=[];$vals=[];foreach($allowed as $k=>$_){}foreach($allowed as $k)if(array_key_exists($k,$changes)){$set[]="$k=?";$vals[]=$changes[$k];}if(!$set)return false;$vals[]=(int)$gameId;$s=reservationDb()->prepare('UPDATE booking_game_settings SET '.implode(',',$set).' WHERE game_id=?');return$s->execute($vals);}
function reservationSlot($slotId){$s=reservationDb()->prepare('SELECT * FROM booking_slots WHERE id=?');$s->execute([(int)$slotId]);return$s->fetch();}
function reservationTemplate($gameId,$weekday,$time){$time=substr(trim($time),0,5);$s=reservationDb()->prepare('SELECT * FROM booking_slot_templates WHERE game_id=? AND weekday=? AND slot_time=?');$s->execute([(int)$gameId,(int)$weekday,$time.':00']);return$s->fetch();}
function reservationSlotStatus($slot){
    if(!$slot)return'missing';
    if(!empty($slot['reserved']))return'reserved';
    if(!empty($slot['manual_closed']))return'closed';
    return'open';
}
function reservationSlotHasPassed($slot){
    if(!$slot||empty($slot['slot_date'])||empty($slot['slot_time']))return false;
    $start=DateTime::createFromFormat('Y-m-d H:i:s',$slot['slot_date'].' '.$slot['slot_time'],reservationTz());
    return $start ? reservationNow()>=$start : false;
}
function reservationEnsureDateSlots($gameId,$date){
    $p=reservationDb(); $weekday=reservationWeekdayIndexFromDate($date);
    $q=$p->prepare('SELECT slot_time,per_person_amount,amount,manual_closed FROM booking_slot_templates WHERE game_id=? AND weekday=? ORDER BY slot_time');
    $q->execute([(int)$gameId,$weekday]);
    foreach($q->fetchAll() as $t){
        $ins=$p->prepare('INSERT INTO booking_slots(game_id,slot_date,slot_time,per_person_amount,amount,manual_closed,reserved) VALUES(?,?,?,?,?,?,0) ON DUPLICATE KEY UPDATE per_person_amount=VALUES(per_person_amount), amount=VALUES(amount), manual_closed=VALUES(manual_closed)');
        $ins->execute([(int)$gameId,$date,$t['slot_time'],(float)$t['per_person_amount'],(float)$t['amount'],(int)$t['manual_closed']]);
    }
    $q=$p->prepare('SELECT id,slot_time,amount,manual_closed,reserved FROM booking_slots WHERE game_id=? AND slot_date=? ORDER BY slot_time');
    $q->execute([(int)$gameId,$date]);
    return $q->fetchAll();
}
function reservationSlots($gameId,$date){
    $p=reservationDb(); $gameId=(int)$gameId; $date=(string)$date;
    if($date<reservationToday()||$date>reservationDays(9))return[];
    $weekday=reservationWeekdayIndexFromDate($date);
    $q=$p->prepare('SELECT id,slot_time,per_person_amount,amount,manual_closed FROM booking_slot_templates WHERE game_id=? AND weekday=? ORDER BY slot_time');
    $q->execute([$gameId,$weekday]); $templates=$q->fetchAll(); if(!$templates)return[];
    $out=[];
    foreach($templates as $t){
        $find=$p->prepare('SELECT * FROM booking_slots WHERE game_id=? AND slot_date=? AND slot_time=? LIMIT 1');
        $find->execute([$gameId,$date,$t['slot_time']]); $slot=$find->fetch();
        if(!$slot){
            try{
                $ins=$p->prepare('INSERT INTO booking_slots(game_id,slot_date,slot_time,per_person_amount,amount,manual_closed,reserved) VALUES(?,?,?,?,?,?,0)');
                $ins->execute([$gameId,$date,$t['slot_time'],(float)$t['per_person_amount'],(float)$t['amount'],(int)$t['manual_closed']]);
                $slot=$p->prepare('SELECT * FROM booking_slots WHERE id=?'); $slot->execute([(int)$p->lastInsertId()]); $slot=$slot->fetch();
            }catch(PDOException $e){ $find->execute([$gameId,$date,$t['slot_time']]); $slot=$find->fetch(); if(!$slot)throw$e; }
        }
        if(!$slot)continue;
        if(empty($slot['reserved'])){
            $up=$p->prepare('UPDATE booking_slots SET per_person_amount=?,amount=?,manual_closed=? WHERE id=? AND reserved=0');
            $up->execute([(float)$t['per_person_amount'],(float)$t['amount'],(int)$t['manual_closed'],(int)$slot['id']]);
            $slot['per_person_amount']=$t['per_person_amount']; $slot['amount']=$t['amount']; $slot['manual_closed']=$t['manual_closed'];
        }
        if(reservationSlotHasPassed($slot))continue;
        $out[]=$slot;
    }
    return$out;
}
function reservationWeeklySlots($gameId,$weekday){$s=reservationDb()->prepare('SELECT * FROM booking_slot_templates WHERE game_id=? AND weekday=? ORDER BY slot_time');$s->execute([(int)$gameId,(int)$weekday]);return$s->fetchAll();}
function reservationAddSlot($gameId,$weekday,$time,$perPersonAmount,$prepaymentAmount){
    $p=reservationDb(); $time=trim((string)$time); $weekday=(int)$weekday;
    if($weekday<0||$weekday>6)throw new InvalidArgumentException('روز هفته نامعتبر است.');
    if(!preg_match('/^([01]\d|2[0-3]):[0-5]\d$/',$time))throw new InvalidArgumentException('فرمت ساعت نامعتبر است. مثال: 17:00');
    $perPersonAmount=(float)$perPersonAmount; $prepaymentAmount=(float)$prepaymentAmount;
    if($perPersonAmount<0||$prepaymentAmount<0)throw new InvalidArgumentException('مبلغ نامعتبر است.');
    $normalized=$time.':00';
    $check=$p->prepare('SELECT id FROM booking_slot_templates WHERE game_id=? AND weekday=? AND slot_time=? LIMIT 1'); $check->execute([(int)$gameId,$weekday,$normalized]);
    if($check->fetch())return false;
    $q=$p->prepare('INSERT INTO booking_slot_templates(game_id,weekday,slot_time,per_person_amount,amount,manual_closed) VALUES(?,?,?,?,?,0)');
    $q->execute([(int)$gameId,$weekday,$normalized,$perPersonAmount,$prepaymentAmount]); return true;
}
function reservationAddSlotAllDays($gameId,$time,$perPersonAmount,$prepaymentAmount){$added=[];$skipped=[];for($weekday=0;$weekday<7;$weekday++){ $ok=reservationAddSlot($gameId,$weekday,$time,$perPersonAmount,$prepaymentAmount);if($ok)$added[]=$weekday;else$skipped[]=$weekday;}return[$added,$skipped];}
function reservationDeleteWeeklySlot($gameId,$weekday,$time){
    $p=reservationDb(); $normalized=substr($time,0,5).':00';
    $q=$p->prepare('SELECT id FROM booking_slot_templates WHERE game_id=? AND weekday=? AND slot_time=?'); $q->execute([(int)$gameId,(int)$weekday,$normalized]);
    if(!$q->fetch())return[false,'این سانس پیدا نشد.'];
    $q=$p->prepare('DELETE FROM booking_slot_templates WHERE game_id=? AND weekday=? AND slot_time=?'); $q->execute([(int)$gameId,(int)$weekday,$normalized]);
    $q=$p->prepare('DELETE FROM booking_slots WHERE game_id=? AND slot_time=? AND reserved=0 AND slot_date>=? AND MOD(WEEKDAY(slot_date)+2,7)=?'); $q->execute([(int)$gameId,$normalized,reservationToday(),(int)$weekday]);
    return[true,'سانس از برنامه این روز حذف شد.'];
}
function reservationSetWeeklyManual($gameId,$weekday,$time,$closed){
    $p=reservationDb();$t=reservationTemplate($gameId,$weekday,$time);if(!$t)return[false,'سانس پیدا نشد.'];
    $q=$p->prepare('UPDATE booking_slot_templates SET manual_closed=? WHERE id=?');$q->execute([$closed?1:0,(int)$t['id']]);
    $q=$p->prepare('UPDATE booking_slots SET manual_closed=? WHERE game_id=? AND slot_time=? AND slot_date>=? AND MOD(WEEKDAY(slot_date)+2,7)=? AND reserved=0');
    $q->execute([$closed?1:0,(int)$gameId,$t['slot_time'],reservationToday(),(int)$weekday]);
    return[true,$closed?'سانس بسته شد.':'سانس باز شد.'];
}
function reservationSetSlotAmount($gameId,$weekday,$time,$amount){$p=reservationDb();$t=reservationTemplate($gameId,$weekday,$time);if(!$t)return[false,'سانس پیدا نشد.'];$amount=(float)$amount;if($amount<0)return[false,'مبلغ نامعتبر است.'];$q=$p->prepare('UPDATE booking_slot_templates SET amount=? WHERE id=?');$q->execute([$amount,(int)$t['id']]);$q=$p->prepare('UPDATE booking_slots SET amount=? WHERE game_id=? AND slot_time=? AND slot_date>=? AND MOD(WEEKDAY(slot_date)+2,7)=? AND reserved=0');$q->execute([$amount,(int)$gameId,$t['slot_time'],reservationToday(),(int)$weekday]);return[true,'مبلغ این سانس ذخیره شد.'];}
function reservationSetSlotPerPerson($gameId,$weekday,$time,$amount){$p=reservationDb();$t=reservationTemplate($gameId,$weekday,$time);if(!$t)return[false,'سانس پیدا نشد.'];$amount=(float)$amount;if($amount<0)return[false,'مبلغ نامعتبر است.'];$q=$p->prepare('UPDATE booking_slot_templates SET per_person_amount=? WHERE id=?');$q->execute([$amount,(int)$t['id']]);$q=$p->prepare('UPDATE booking_slots SET per_person_amount=? WHERE game_id=? AND slot_time=? AND slot_date>=? AND MOD(WEEKDAY(slot_date)+2,7)=? AND reserved=0');$q->execute([$amount,(int)$gameId,$t['slot_time'],reservationToday(),(int)$weekday]);return[true,'مبلغ هر نفر این سانس ذخیره شد.'];}
function reservationGenerateCode($pdo){$chars='ABCDEFGHJKLMNPQRSTUVWXYZ23456789';for($try=0;$try<30;$try++){ $code='BR-';for($i=0;$i<7;$i++)$code.=$chars[random_int(0,strlen($chars)-1)];$q=$pdo->prepare('SELECT id FROM reservations WHERE booking_code=? LIMIT 1');$q->execute([$code]);if(!$q->fetch())return$code;}throw new Exception('خطا در ساخت شماره رزرو.');}
function reservationDeleteSlot($id){$s=reservationSlot($id);if(!$s)return[false,'سانس پیدا نشد.'];if(!empty($s['reserved']))return[false,'این سانس رزرو شده و قابل حذف نیست.'];return reservationDeleteWeeklySlot((int)$s['game_id'],reservationWeekdayIndexFromDate($s['slot_date']),substr($s['slot_time'],0,5));}
function reservationCreate($slotId,$gameId,$userId,$players,$receiptFileId,$amount,$fullName,$receiptType='photo'){
    $p=reservationDb();$p->beginTransaction();
    try{
        $q=$p->prepare('SELECT * FROM booking_slots WHERE id=? FOR UPDATE');$q->execute([(int)$slotId]);$slot=$q->fetch();
        if(!$slot||$slot['game_id']!=(int)$gameId)throw new Exception('سانس پیدا نشد.');
        if(reservationSlotHasPassed($slot)||reservationSlotStatus($slot)!=='open')throw new Exception('این سانس دیگر قابل رزرو نیست.');
        $q=$p->prepare("SELECT id FROM reservations WHERE slot_id=? AND status IN ('pending','approved') LIMIT 1 FOR UPDATE");$q->execute([(int)$slotId]);
        if($q->fetch())throw new Exception('این سانس همین الان رزرو شد.');
        $prepayment=(float)$slot['amount'];$per=(float)$slot['per_person_amount'];$code=reservationGenerateCode($p);
        $q=$p->prepare("INSERT INTO reservations(slot_id,game_id,user_id,players,amount,per_person_amount,booking_code,full_name,receipt_file_id,receipt_type,status) VALUES(?,?,?,?,?,?,?,?,?,?,'pending')");
        $q->execute([(int)$slotId,(int)$gameId,(int)$userId,(int)$players,$prepayment,$per,$code,trim((string)$fullName),$receiptFileId,$receiptType]);
        $id=$p->lastInsertId();$q=$p->prepare('UPDATE booking_slots SET reserved=1 WHERE id=?');$q->execute([(int)$slotId]);$p->commit();return(int)$id;
    }catch(Throwable $e){if($p->inTransaction())$p->rollBack();throw$e;}
}
function reservationGet($id){$s=reservationDb()->prepare('SELECT r.*,s.slot_date,s.slot_time,s.amount AS slot_amount,COALESCE(NULLIF(r.per_person_amount,0),NULLIF(s.per_person_amount,0),t.per_person_amount,0) AS effective_per_person_amount FROM reservations r JOIN booking_slots s ON s.id=r.slot_id LEFT JOIN booking_slot_templates t ON t.game_id=s.game_id AND t.weekday=MOD(WEEKDAY(s.slot_date)+2,7) AND t.slot_time=s.slot_time WHERE r.id=?');$s->execute([(int)$id]);$row=$s->fetch();if($row){$row['per_person_amount']=(float)$row['effective_per_person_amount'];if($row['per_person_amount']>0){try{$u=reservationDb()->prepare('UPDATE reservations SET per_person_amount=? WHERE id=? AND (per_person_amount IS NULL OR per_person_amount=0)');$u->execute([$row['per_person_amount'],(int)$id]);}catch(Throwable $e){}}}return$row;}
function reservationUpdateStatus($id,$status,$by,$reason=''){$p=reservationDb();$r=reservationGet($id);if(!$r)throw new Exception('رزرو پیدا نشد.');if($r['status']!=='pending')throw new Exception('این رزرو قبلاً بررسی شده.');$s=$p->prepare('UPDATE reservations SET status=?,reject_reason=?,handled_by=?,approved_at=?,rejected_at=? WHERE id=?');$s->execute([$status,$reason?:null,(int)$by,$status==='approved'?date('Y-m-d H:i:s'):null,$status==='rejected'?date('Y-m-d H:i:s'):null,(int)$id]);if($status==='rejected'){$s=$p->prepare('UPDATE booking_slots SET reserved=0 WHERE id=?');$s->execute([(int)$r['slot_id']]);}return reservationGet($id);}
function reservationTotalAmount($slot,$players){$per=(float)($slot['per_person_amount']??0);$pre=(float)($slot['amount']??0);$total=max(0,$per*(int)$players);return[$total,max(0,$total-$pre)];}
function reservationPaymentText($game,$slot=null,$players=null){
    $settings=reservationGameSettings($game['id']);$t="💳 <b>اطلاعات واریز</b>\n\n";
    if(trim($settings['card_number']??''))$t.="💳 شماره کارت: <code>".escapeHtml($settings['card_number'])."</code>\n";
    if(trim($settings['card_holder']??''))$t.="👤 صاحب کارت: <b>".escapeHtml($settings['card_holder'])."</b>\n";
    if($slot!==null){$per=(float)($slot['per_person_amount']??0);$pre=(float)($slot['amount']??0);$t.="👤 مبلغ هر نفر: <code>".number_format($per,0,'.',',')." تومان</code>\n";if($players!==null){[$total,$remaining]=reservationTotalAmount($slot,(int)$players);$t.="👥 تعداد نفرات: <code>".(int)$players." نفر</code>\n";$t.="💰 مبلغ کل: <code>".number_format($total,0,'.',',')." تومان</code>\n";$t.="💳 مبلغ پیش پرداخت این سانس: <code>".number_format($pre,0,'.',',')." تومان</code>\n";$t.="💵 مانده پرداخت در محل: <code>".number_format($remaining,0,'.',',')." تومان</code>\n";}elseif($pre>0)$t.="💳 مبلغ پیش پرداخت این سانس: <code>".number_format($pre,0,'.',',')." تومان</code>\n";}
    return rtrim($t);
}
function reservationUserDateMenu($gameId,$offset=0){$rows=[];for($i=0;$i<10;$i++){$d=reservationDays($i);$rows[]=[['text'=>reservationDayLabel($d),'callback_data'=>'book:date:'.$gameId.':'.$d]];}return inlineKeyboard($rows);}
function reservationSlotsMenu($gameId,$date){$rows=[];$templates=reservationWeeklySlots($gameId,reservationWeekdayIndexFromDate($date));$templatePer=[];foreach($templates as $t)$templatePer[substr($t['slot_time'],0,5)]=(float)$t['per_person_amount'];foreach(reservationSlots($gameId,$date)as$s){$st=reservationSlotStatus($s);$time=substr($s['slot_time'],0,5);$per=(float)($s['per_person_amount']??0);if($per<=0&&isset($templatePer[$time]))$per=$templatePer[$time];$prefix=$st==='open'?'🟢 ':($st==='reserved'?'🔴 ':'⚫ ');$label=$prefix.$time.' — هر نفر '.reservationFaDigits(number_format($per,0,'.',',')).' تومان';$rows[]=[['text'=>$label,'callback_data'=>$st==='open'?'book:slot:'.$s['id']:'noop']];}if(!$rows)$rows[]=[['text'=>'❌ برای این روز سانسی قابل رزرو وجود ندارد','callback_data'=>'noop']];$rows[]=[['text'=>'↩️ تاریخ‌ها','callback_data'=>'book:dates:'.$gameId.':0']];return inlineKeyboard($rows);}
function reservationAdminMenu($gameId,$chatId=null){$priv=$chatId===null||isOwner($chatId)||isFounder($chatId);$rows=[[['text'=>'🕐 مدیریت سانس‌ها','callback_data'=>'bookadmin:slots:'.$gameId]],[['text'=>'✅ رزروهای انجام شده','callback_data'=>'bookadmin:completed:'.$gameId]]];if($priv){$rows[]=[['text'=>'💳 تنظیمات واریز','callback_data'=>'bookadmin:pay:'.$gameId]];$rows[]=[['text'=>'📨 مقصد رسیدها','callback_data'=>'bookadmin:routing:'.$gameId]];$rows[]=[['text'=>'📥 رسیدهای در انتظار','callback_data'=>'bookadmin:pending:'.$gameId]];}$rows[]=[['text'=>'↩️ مدیریت بازی','callback_data'=>'admin:game:'.$gameId]];return inlineKeyboard($rows);}
function reservationFounderMenu(){return inlineKeyboard([[['text'=>reservationEnabled()?'🔴 خاموش کردن رزرو جدید':'🟢 روشن کردن رزرو جدید','callback_data'=>'founder:reservation_toggle']],[['text'=>'↩️ کنترل سیستم','callback_data'=>'founder:system']]]);}
function reservationCanManage($chatId,$gameId){return isOwner($chatId)||isFounder($chatId)||canManageGame($chatId,$gameId);} function reservationReviewRecipients($gameId){return array_values(array_unique(array_filter(array_map('intval',getConfiguredOwnerIds()))));}
function reservationApprovedRecipients($gameId){$ids=reservationReviewRecipients($gameId);foreach(getGameAdmins($gameId)as$a)$ids[]=(int)$a['id'];return array_values(array_unique(array_filter(array_map('intval',$ids))));}
function reservationNotifyRecipients($gameId){return reservationApprovedRecipients($gameId);}
function reservationNotifyApprovedAdmins($reservation,$game){$u=getUser((int)$reservation['user_id']);$username=trim((string)($u['username']??''));$usernameText=$username!==''?(strpos($username,'@')===0?$username:'@'.$username):'ندارد';[$total,$remaining]=reservationTotalAmount($reservation,(int)$reservation['players']);$txt="🎉 <b>رزرو با موفقیت تأیید شد</b>\n\n🆔 شماره رزرو: <code>".escapeHtml($reservation['booking_code'])."</code>\n🎮 بازی: <b>".escapeHtml($game['name'])."</b>\n👤 نام و نام خانوادگی: <b>".escapeHtml($reservation['full_name'])."</b>\n🆔 آیدی عددی: <code>".(int)$reservation['user_id']."</code>\n🔗 یوزرنیم: <b>".escapeHtml($usernameText)."</b>\n📅 تاریخ: <b>".escapeHtml(reservationDayLabel($reservation['slot_date']))."</b>\n🕐 سانس: <b>".substr($reservation['slot_time'],0,5)."</b>\n👥 تعداد نفرات: <b>".(int)$reservation['players']."</b>\n👤 مبلغ هر نفر: <b>".number_format((float)$reservation['per_person_amount'],0,'.',',')." تومان</b>\n💰 مبلغ کل: <b>".number_format($total,0,'.',',')." تومان</b>\n💳 مبلغ پیش پرداخت: <b>".number_format((float)$reservation['amount'],0,'.',',')." تومان</b>\n💵 مانده پرداخت در محل: <b>".number_format($remaining,0,'.',',')." تومان</b>";foreach(reservationApprovedRecipients((int)$reservation['game_id'])as$to)sendMessage($to,$txt);}
function reservationStart($chatId,$gameId){if(!reservationEnabled()){sendMessage($chatId,'سیستم رزرو جدید فعلاً فعال نیست.');return;}$g=getGame($gameId);if(!$g||empty($g['active'])||empty($g['reservation_enabled'])){sendMessage($chatId,'این بازی فعال نیست.');return;}saveUser($chatId,['reservation_flow'=>'date','reservation_game_id'=>(int)$gameId,'reservation_offset'=>0]);sendMessage($chatId,"📅 <b>تاریخ رزرو را انتخاب کن</b>\n\nتا ۱۰ روز آینده در دسترس است:",reservationUserDateMenu($gameId,0));}
function reservationHandleMessage($message){
    if(!reservationEnabled())return false;$chatId=(int)$message['chat']['id'];$u=getUser($chatId);$flow=$u['reservation_flow']??'';$text=cleanTelegramText($message['text']??'');
    if($flow==='name'){if($text===''||mb_strlen($text)<3||count(preg_split('/\s+/u',$text))<2){sendMessage($chatId,'❌ لطفاً نام و نام خانوادگی را کامل وارد کن.\nمثال: <code>علی رضایی</code>');return true;}$u['reservation_full_name']=$text;$u['reservation_flow']='players';saveUser($chatId,$u);sendMessage($chatId,'👥 <b>چند نفر هستید؟</b>\n\nتعداد نفرات را فقط به صورت عدد بفرست.',cancelButton());return true;}
    if($flow==='players'){if(!ctype_digit($text)||((int)$text)<1){sendMessage($chatId,'❌ تعداد نفرات را به صورت عدد وارد کن.');return true;}$n=(int)$text;$slot=reservationSlot((int)$u['reservation_slot_id']);if(!$slot||reservationSlotHasPassed($slot)||reservationSlotStatus($slot)!=='open'){sendMessage($chatId,'❌ این سانس دیگر قابل رزرو نیست.');deleteUser($chatId);return true;}$u['reservation_players']=$n;$u['reservation_amount']=(float)$slot['amount'];saveUser($chatId,$u);$pay=reservationPaymentText(getGame($u['reservation_game_id']),$slot,$n);$pay.="\n\n📸 حالا <b>رسید واریز</b> را به صورت عکس یا فایل بفرست.";$u['reservation_flow']='receipt';saveUser($chatId,$u);sendMessage($chatId,$pay,cancelButton());return true;}
    if($flow==='receipt'){ $file=extractTelegramFile($message);if(!$file){sendMessage($chatId,'❌ لطفاً عکس یا فایل رسید را ارسال کن.');return true;}try{$from=$message['from']??[];$rid=reservationCreate($u['reservation_slot_id'],$u['reservation_game_id'],$chatId,$u['reservation_players'],$file['file_id'],$u['reservation_amount'],$u['reservation_full_name']??'',$file['type']);saveUser($chatId,['reservation_flow'=>'','reservation_id'=>$rid]);$r=reservationGet($rid);$g=getGame($r['game_id']);$username=trim((string)($from['username']??''));$usernameText=$username!==''?'@'.$username:'ندارد';[$total,$remaining]=reservationTotalAmount(['per_person_amount'=>$r['per_person_amount'],'amount'=>$r['amount']],(int)$r['players']);$txt="📥 <b>رسید رزرو جدید</b>\n\n🎮 بازی: <b>".escapeHtml($g['name'])."</b>\n🆔 شماره رزرو: <code>".escapeHtml($r['booking_code'])."</code>\n👤 نام و نام خانوادگی: <b>".escapeHtml($r['full_name'])."</b>\n🆔 آیدی عددی: <code>".(int)$chatId."</code>\n🔗 یوزرنیم: <b>".escapeHtml($usernameText)."</b>\n📅 تاریخ: <b>".escapeHtml(reservationDayLabel($r['slot_date']))."</b>\n🕐 سانس: <b>".substr($r['slot_time'],0,5)."</b>\n👥 تعداد نفرات: <b>".(int)$r['players']."</b>\n👤 مبلغ هر نفر: <b>".number_format((float)$r['per_person_amount'],0,'.',',')." تومان</b>\n💰 مبلغ کل: <b>".number_format($total,0,'.',',')." تومان</b>\n💳 مبلغ پیش پرداخت: <b>".number_format((float)$r['amount'],0,'.',',')." تومان</b>\n💵 مانده پرداخت در محل: <b>".number_format($remaining,0,'.',',')." تومان</b>";$kb=inlineKeyboard([[['text'=>'✅ تایید','callback_data'=>'bookadmin:approve:'.$rid],['text'=>'❌ رد','callback_data'=>'bookadmin:reject:'.$rid]]]);foreach(reservationReviewRecipients($r['game_id'])as$to){if($file['type']==='photo')sendPhoto($to,$file['file_id'],$txt,$kb);else sendMessage($to,$txt,$kb);}sendMessage($chatId,'🎉 <b>رسیدت بهمون رسید😉</b>\n\n✨ تو اولین فرصت بررسیش می‌کنیم و نتیجه رو همینجا بهت اعلام می‌کنیم.',mainMenu());}catch(Throwable $e){sendMessage($chatId,'❌ '.escapeHtml($e->getMessage()));}return true;}return false;
}
function reservationHandleCallback($callback){
    $data=$callback['data']??'';$chatId=(int)($callback['message']['chat']['id']??$callback['from']['id']);$from=(int)($callback['from']['id']??0);$id=$callback['id']??'';$parts=explode(':',$data);
    if(!reservationEnabled()&&strpos($data,'founder:reservation')!==0&&strpos($data,'bookadmin:')!==0)return false;
    if(strpos($data,'founder:reservation_toggle')===0){if(!isFounder($from)){answerCallback($id,'دسترسی نداری.',true);return true;}setReservationEnabled(!reservationEnabled(),$from);answerCallback($id);sendMessage($chatId,reservationEnabled()?'🟢 سیستم رزرو جدید روشن شد.':'🔴 سیستم رزرو جدید خاموش شد.',reservationFounderMenu());return true;}
    if(strpos($data,'reserve:')===0){reservationStart($chatId,(int)substr($data,8));return true;}
    if(strpos($data,'book:dates:')===0){$gid=(int)($parts[2]??0);answerCallback($id);sendMessage($chatId,'📅 <b>تاریخ موردنظر را انتخاب کن:</b>',reservationUserDateMenu($gid,0));return true;}
    if(strpos($data,'book:date:')===0){$gid=(int)($parts[2]??0);$date=$parts[3]??'';$g=getGame($gid);if(!$g||empty($g['active'])||empty($g['reservation_enabled'])){answerCallback($id,'این بازی برای رزرو فعال نیست.',true);return true;}if(!preg_match('/^\d{4}-\d{2}-\d{2}$/',$date)||$date<reservationToday()||$date>reservationDays(9)){answerCallback($id,'این تاریخ خارج از بازه رزرو است.',true);return true;}try{$slots=reservationSlots($gid,$date);answerCallback($id);$text='🕐 <b>'.escapeHtml(reservationDayLabel($date)).'</b>\n\n';$text.=$slots?'🟢 قابل رزرو   🔴 رزرو شده   ⚫ بسته':'❌ برای این روز سانسی قابل رزرو وجود ندارد.';sendMessage($chatId,$text,reservationSlotsMenu($gid,$date));}catch(Throwable $e){answerCallback($id,'خطا در بارگذاری سانس‌ها.',true);sendMessage($chatId,'❌ خطا در بارگذاری سانس‌ها: '.escapeHtml($e->getMessage()));}return true;}
    if(strpos($data,'book:slot:')===0){$sid=(int)substr($data,10);$slot=reservationSlot($sid);if(!$slot||reservationSlotHasPassed($slot)||reservationSlotStatus($slot)!=='open'){answerCallback($id,'این سانس دیگر قابل رزرو نیست.',true);return true;}answerCallback($id);saveUser($chatId,['reservation_flow'=>'name','reservation_slot_id'=>$sid,'reservation_game_id'=>(int)$slot['game_id']]);sendMessage($chatId,'👤 <b>نام و نام خانوادگی</b> را وارد کن:\n\nمثال: <code>علی رضایی</code>',cancelButton());return true;}
    if(strpos($data,'bookadmin:')===0){$action=$parts[1]??'';$gid=(int)($parts[2]??0);
        if($action==='reservation_list'){if(!isOwner($from)&&!isFounder($from)){answerCallback($id,'این بخش فقط برای Owner و Founder است.',true);return true;}answerCallback($id);$rows=[];foreach(getGames() as $g)if(!empty($g['active']))$rows[]=[['text'=>'🎮 '.($g['name']??''),'callback_data'=>'bookadmin:reservation_game:'.(int)$g['id']]];if(!$rows)$rows[]=[['text'=>'📭 بازی‌ای وجود ندارد','callback_data'=>'noop']];$rows[]=[['text'=>'↩️ منوی ادمین','callback_data'=>'admin:user_menu']];sendMessage($from,'🎟 <b>رزرو رسیدی‌ها</b>\n\nبازی موردنظر را انتخاب کن:',inlineKeyboard($rows));return true;}
        if($action==='reservation_game'){if(!isOwner($from)&&!isFounder($from)){answerCallback($id,'این بخش فقط برای Owner و Founder است.',true);return true;}answerCallback($id);reservationAllReservations($from,$gid);return true;}
        if($action==='completed'){if(!reservationCanManage($from,$gid)){answerCallback($id,'دسترسی نداری.',true);return true;}reservationCompleted($from,$gid);return true;}
        if(in_array($action,['viewdone','viewall','view','receipt'],true)){ $rid=(int)$gid;$r=reservationGet($rid);if(!$r){answerCallback($id,'رزرو پیدا نشد.',true);return true;}if($action==='viewall'&&!isOwner($from)&&!isFounder($from)){answerCallback($id,'این بخش فقط برای Owner و Founder است.',true);return true;}if($action!=='viewall'&&!reservationCanManage($from,$r['game_id'])){answerCallback($id,'دسترسی نداری.',true);return true;}if($action==='receipt'){answerCallback($id);reservationSendReceipt($from,$r);return true;}$g=getGame($r['game_id']);answerCallback($id);$back=($action==='viewall'||$action==='view')?'bookadmin:reservation_game:'.$r['game_id']:'bookadmin:completed:'.$r['game_id'];$kb=[];if(!empty($r['receipt_file_id']))$kb[]=[['text'=>'🧾 دریافت رسید این رزرو','callback_data'=>'bookadmin:receipt:'.$rid]];if($action==='view'&&$r['status']==='pending'&&(isOwner($from)||isFounder($from)))$kb[]=[['text'=>'✅ تایید','callback_data'=>'bookadmin:approve:'.$rid],['text'=>'❌ رد','callback_data'=>'bookadmin:reject:'.$rid]];$kb[]=[['text'=>'↩️ برگشت','callback_data'=>$back]];sendMessage($from,reservationBookingDetailsText($r,$g),inlineKeyboard($kb));return true;}
        if(in_array($action,['approve','reject'],true)){$rid=$gid;$r=reservationGet($rid);if(!$r||(!isOwner($from)&&!isFounder($from))){answerCallback($id,'فقط Owner و Founder می‌توانند رسید را بررسی کنند.',true);return true;}if($action==='reject'){setAdminState($from,['action'=>'reservation_reject_reason','reservation_id'=>$rid]);answerCallback($id);sendMessage($from,'❌ دلیل رد این رسید را بنویس:',cancelButton());return true;}try{$old=reservationUpdateStatus($rid,'approved',$from);$g=getGame($old['game_id']);reservationNotifyApprovedAdmins($old,$g);[$total,$remaining]=reservationTotalAmount(['per_person_amount'=>$old['per_person_amount'],'amount'=>$old['amount']],(int)$old['players']);$msg="🎉 <b>رزرو شما با موفقیت تأیید شد</b>\n\n🆔 شماره رزرو: <code>".escapeHtml($old['booking_code'])."</code>\n🎮 بازی: <b>".escapeHtml($g['name'])."</b>\n👤 نام و نام خانوادگی: <b>".escapeHtml($old['full_name'])."</b>\n📅 تاریخ: <b>".escapeHtml(reservationDayLabel($old['slot_date']))."</b>\n🕐 سانس: <b>".substr($old['slot_time'],0,5)."</b>\n👥 تعداد نفرات: <b>".(int)$old['players']."</b>\n👤 مبلغ هر نفر: <b>".number_format((float)$old['per_person_amount'],0,'.',',')." تومان</b>\n💰 مبلغ کل: <b>".number_format($total,0,'.',',')." تومان</b>\n💳 مبلغ پیش پرداخت: <b>".number_format((float)$old['amount'],0,'.',',')." تومان</b>\n💵 مانده پرداخت در محل: <b>".number_format($remaining,0,'.',',')." تومان</b>\n\n📍 آدرس:\n".escapeHtml($g['address']??'')."\n\n📞 شماره تماس:\n".escapeHtml($g['phone']??'')."\n\n⏰ لطفاً ۱۵ دقیقه قبل در محل حضور داشته باشید.\n\n📋 بعد از این، حتماً به بخش <b>رزرو کردم و برای ثبت اطلاعات اومدم</b> برو و اطلاعاتت رو ثبت کن تا اطلاعات بازیت کامل ثبت بشه. 🙏";sendMessage($old['user_id'],$msg,mainMenu());$mid=(int)($callback['message']['message_id']??0);if($mid>0){$summary="🎉 <b>رزرو با موفقیت تأیید شد</b>\n\n🆔 شماره رزرو: <code>".escapeHtml($old['booking_code'])."</code>\n🎮 بازی: <b>".escapeHtml($g['name'])."</b>\n👤 نام و نام خانوادگی: <b>".escapeHtml($old['full_name'])."</b>\n🆔 آیدی عددی: <code>".(int)$old['user_id']."</code>\n📅 تاریخ: <b>".escapeHtml(reservationDayLabel($old['slot_date']))."</b>\n🕐 سانس: <b>".substr($old['slot_time'],0,5)."</b>\n👥 تعداد نفرات: <b>".(int)$old['players']."</b>\n👤 مبلغ هر نفر: <b>".number_format((float)$old['per_person_amount'],0,'.',',')." تومان</b>\n💰 مبلغ کل: <b>".number_format($total,0,'.',',')." تومان</b>\n💳 مبلغ پیش پرداخت: <b>".number_format((float)$old['amount'],0,'.',',')." تومان</b>\n💵 مانده پرداخت در محل: <b>".number_format($remaining,0,'.',',')." تومان</b>";if(!empty($callback['message']['photo'])||!empty($callback['message']['document']))editMessageCaption($chatId,$mid,$summary,null);else editMessage($chatId,$mid,$summary,null);}answerCallback($id,'رزرو تأیید شد.');}catch(Throwable $e){answerCallback($id,$e->getMessage(),true);}return true;}
        if(in_array($action,['pending','pay','routing'],true)&&!isOwner($from)&&!isFounder($from)){answerCallback($id,'این بخش فقط برای Owner و Founder است.',true);return true;}
        if($action==='slots'){if(!reservationCanManage($from,$gid)){answerCallback($id,'دسترسی نداری.',true);return true;}answerCallback($id);sendMessage($from,'🕐 <b>مدیریت سانس‌ها</b>\n\nروز هفته را انتخاب کن:',reservationAdminDateMenu($gid));return true;}
        if($action==='pay'){answerCallback($id);sendMessage($from,'💳 <b>تنظیمات واریز</b>\n\nشماره کارت و نام صاحب کارت را برای این بازی تنظیم کن.',reservationPayMenu($gid));return true;}
        if($action==='routing'){answerCallback($id);$ss=reservationGameSettings($gid);sendMessage($from,'📨 <b>مقصد رسیدهای این بازی</b>',inlineKeyboard([[['text'=>($ss['receipt_routing']==='owners'?'✅ ':'⬜ ').'فقط Owner + Founder','callback_data'=>'bookadmin:route:'.$gid.':owners']],[['text'=>($ss['receipt_routing']==='admins'?'✅ ':'⬜ ').'ادمین‌های بازی + Owner + Founder','callback_data'=>'bookadmin:route:'.$gid.':admins']],[['text'=>'↩️ برگشت','callback_data'=>'admin:game:'.$gid]]]));return true;}
        if($action==='pending'){reservationPending($from,$gid);return true;}
        if($action==='slotsday'){$weekday=(int)($parts[3]??-1);if(!reservationCanManage($from,$gid)){answerCallback($id,'دسترسی نداری.',true);return true;}answerCallback($id);sendMessage($from,'🕐 <b>'.reservationWeekdayName($weekday).'</b>\n\nسانس‌های این روز:',reservationAdminSlotsMenu($gid,$weekday));return true;}
        if($action==='addslot'||$action==='addslotall'){if(!reservationCanManage($from,$gid)){answerCallback($id,'دسترسی نداری.',true);return true;}$state=['action'=>$action==='addslotall'?'reservation_addslot_all':'reservation_addslot','game_id'=>$gid];if($action==='addslot')$state['weekday']=(int)($parts[3]??-1);setAdminState($from,$state);answerCallback($id);sendMessage($from,$action==='addslotall'?'➕ ساعت سانس سراسری را به فرمت HH:MM بفرست.\nمثال: <code>17:00</code>':'➕ ساعت سانس برای <b>'.reservationWeekdayName($state['weekday']).'</b> را به فرمت HH:MM بفرست.\nمثال: <code>17:00</code>',cancelButton());return true;}
        if($action==='delslot'){$weekday=(int)($parts[3]??-1);$rows=[];foreach(reservationWeeklySlots($gid,$weekday)as$t)$rows[]=[['text'=>'🗑 '.substr($t['slot_time'],0,5),'callback_data'=>'bookadmin:deleteweekly:'.$gid.':'.$weekday.':'.substr($t['slot_time'],0,5)]];$rows[]=[['text'=>'↩️ برگشت','callback_data'=>'bookadmin:slotsday:'.$gid.':'.$weekday]];answerCallback($id);sendMessage($from,'🗑 سانس موردنظر را برای حذف انتخاب کن:',inlineKeyboard($rows));return true;}
        if($action==='deleteweekly'){$weekday=(int)($parts[3]??-1);[$ok,$msg]=reservationDeleteWeeklySlot($gid,$weekday,$parts[4]??'');answerCallback($id,$msg,!$ok);sendMessage($from,$msg,reservationAdminSlotsMenu($gid,$weekday));return true;}
        if($action==='toggleweekly'){$weekday=(int)($parts[3]??-1);$time=$parts[4]??'';$t=reservationTemplate($gid,$weekday,$time);if(!$t){answerCallback($id,'سانس پیدا نشد.',true);return true;}[$ok,$msg]=reservationSetWeeklyManual($gid,$weekday,$time,!empty($t['manual_closed'])?false:true);answerCallback($id,$msg,!$ok);sendMessage($from,$msg,reservationAdminSlotsMenu($gid,$weekday));return true;}
        if($action==='slotperperson'||$action==='slotamount'){$weekday=(int)($parts[3]??-1);$time=$parts[4]??'';$t=reservationTemplate($gid,$weekday,$time);if(!$t){answerCallback($id,'سانس پیدا نشد.',true);return true;}setAdminState($from,['action'=>$action==='slotperperson'?'reservation_slot_perperson':'reservation_slot_amount','game_id'=>$gid,'weekday'=>$weekday,'slot_time'=>$time]);answerCallback($id);sendMessage($from,$action==='slotperperson'?'👤 مبلغ هر نفر برای این سانس را به تومان وارد کن.':'💳 مبلغ پیش پرداخت این سانس را به تومان وارد کن.',cancelButton());return true;}
        if($action==='payslots'||$action==='payday'){answerCallback($id);sendMessage($from,$action==='payslots'?'💰 مبلغ هر سانس برای هر روز در مدیریت سانس‌ها تنظیم می‌شود.':('💰 '.reservationWeekdayName((int)($parts[3]??0))),reservationPayMenu($gid));return true;}
        if($action==='setcard'||$action==='setholder'){setAdminState($from,['action'=>$action==='setcard'?'reservation_card':'reservation_holder','game_id'=>$gid]);answerCallback($id);sendMessage($from,$action==='setcard'?'💳 شماره کارت را بفرست:':'👤 نام و نام خانوادگی صاحب کارت را بفرست:',cancelButton());return true;}
        if($action==='route'){reservationSaveGameSettings($gid,['receipt_routing'=>$parts[3]??'owners']);answerCallback($id,'ذخیره شد.');sendMessage($from,'✅ مقصد رسیدها ذخیره شد.',reservationAdminMenu($gid,$from));return true;}
    }
    return false;
}
function reservationAdminDateMenu($gid){$rows=[];for($i=0;$i<7;$i++)$rows[]=[['text'=>reservationWeekdayName($i),'callback_data'=>'bookadmin:slotsday:'.$gid.':'.$i]];$rows[]=[['text'=>'↩️ مدیریت بازی','callback_data'=>'admin:game:'.$gid]];return inlineKeyboard($rows);}
function reservationAdminSlotsMenu($gid,$weekday){$rows=[];foreach(reservationWeeklySlots($gid,$weekday)as$t){$time=substr($t['slot_time'],0,5);$closed=!empty($t['manual_closed']);$rows[]=[['text'=>($closed?'🔴 بسته ':'🟢 باز ').$time,'callback_data'=>'bookadmin:toggleweekly:'.$gid.':'.$weekday.':'.$time],['text'=>'👤 '.number_format((float)$t['per_person_amount'],0,'.',',').' تومان','callback_data'=>'bookadmin:slotperperson:'.$gid.':'.$weekday.':'.$time],['text'=>'💳 پیش‌پرداخت '.number_format((float)$t['amount'],0,'.',',').' تومان','callback_data'=>'bookadmin:slotamount:'.$gid.':'.$weekday.':'.$time]];}$rows[]=[['text'=>'➕ افزودن سانس این روز','callback_data'=>'bookadmin:addslot:'.$gid.':'.$weekday],['text'=>'➕ افزودن سانس همه روزها','callback_data'=>'bookadmin:addslotall:'.$gid]];$rows[]=[['text'=>'🗑 حذف سانس','callback_data'=>'bookadmin:delslot:'.$gid.':'.$weekday]];$rows[]=[['text'=>'📅 انتخاب روز دیگر','callback_data'=>'bookadmin:slots:'.$gid]];return inlineKeyboard($rows);}
function reservationPayMenu($gid){return inlineKeyboard([[['text'=>'💳 شماره کارت','callback_data'=>'bookadmin:setcard:'.$gid]],[['text'=>'👤 صاحب کارت','callback_data'=>'bookadmin:setholder:'.$gid]],[['text'=>'↩️ برگشت','callback_data'=>'admin:game:'.$gid]]]);}
function reservationPayDayMenu($gid){$rows=[];for($i=0;$i<7;$i++)$rows[]=[['text'=>reservationWeekdayName($i),'callback_data'=>'bookadmin:payday:'.$gid.':'.$i]];$rows[]=[['text'=>'↩️ تنظیمات واریز','callback_data'=>'bookadmin:pay:'.$gid]];return inlineKeyboard($rows);}
function reservationPaySlotsMenu($gid,$weekday){$rows=[];foreach(reservationWeeklySlots($gid,$weekday)as$s){$time=substr($s['slot_time'],0,5);$rows[]=[['text'=>'🕐 '.$time.' — 👤 '.number_format((float)($s['per_person_amount']??0),0,'.',',').' تومان','callback_data'=>'bookadmin:slotperperson:'.$gid.':'.$weekday.':'.$time],['text'=>'💳 پیش‌پرداخت '.number_format((float)$s['amount'],0,'.',',').' تومان','callback_data'=>'bookadmin:slotamount:'.$gid.':'.$weekday.':'.$time]];}$rows[]=[['text'=>'↩️ روزها','callback_data'=>'bookadmin:payslots:'.$gid]];return inlineKeyboard($rows);}
function reservationPending($chatId,$gid){if(!isOwner($chatId)&&!isFounder($chatId)){sendMessage($chatId,'⛔ این بخش فقط برای Owner و Founder است.',adminMenu($chatId));return;}$p=reservationDb();$q=$p->prepare("SELECT id,booking_code,players,amount,created_at FROM reservations WHERE game_id=? AND status='pending' ORDER BY id DESC LIMIT 50");$q->execute([(int)$gid]);$rows=[];foreach($q->fetchAll()as$r)$rows[]=[['text'=>'📥 '.($r['booking_code']?:('BR-'.$r['id'])).' | 👥'.(int)$r['players'].' | '.number_format((float)$r['amount'],0,'.',',').' تومان','callback_data'=>'bookadmin:view:'.$r['id']]];$rows[]=[['text'=>'↩️ برگشت','callback_data'=>'admin:game:'.$gid]];sendMessage($chatId,'📥 <b>رسیدهای در انتظار</b>',inlineKeyboard($rows));}
function reservationAllReservations($chatId,$gid){if(!isOwner($chatId)&&!isFounder($chatId)){sendMessage($chatId,'⛔ این بخش فقط برای Owner و Founder است.',adminMenu($chatId));return;}$p=reservationDb();$q=$p->prepare('SELECT id,booking_code,full_name,players,amount,status,slot_id FROM reservations WHERE game_id=? ORDER BY id DESC LIMIT 100');$q->execute([(int)$gid]);$rows=[];foreach($q->fetchAll()as$r){$icon=$r['status']==='approved'?'✅':($r['status']==='pending'?'⏳':'❌');$rows[]=[['text'=>$icon.' '.($r['booking_code']?:('BR-'.$r['id'])).' | '.trim((string)$r['full_name']).' | 👥'.(int)$r['players'],'callback_data'=>'bookadmin:viewall:'.$r['id']]];}if(!$rows)$rows[]=[['text'=>'📭 رزروی ثبت نشده','callback_data'=>'noop']];$rows[]=[['text'=>'↩️ برگشت','callback_data'=>'admin:reservation_list']];sendMessage($chatId,'🎟 <b>رزرو رسیدی‌ها</b>\n\nهمه رزروهای این بازی:',inlineKeyboard($rows));}
function reservationCompleted($chatId,$gid){if(!reservationCanManage($chatId,$gid)){sendMessage($chatId,'⛔ دسترسی نداری.',adminMenu($chatId));return;}$p=reservationDb();$q=$p->prepare("SELECT id,booking_code,full_name,players,amount,per_person_amount,slot_id FROM reservations WHERE game_id=? AND status='approved' ORDER BY id DESC LIMIT 50");$q->execute([(int)$gid]);$rows=[];foreach($q->fetchAll()as$r)$rows[]=[['text'=>'✅ '.($r['booking_code']?:('BR-'.$r['id'])).' | '.trim((string)$r['full_name']).' | 👥'.(int)$r['players'],'callback_data'=>'bookadmin:viewdone:'.$r['id']]];$rows[]=[['text'=>'↩️ برگشت','callback_data'=>'admin:game:'.$gid]];sendMessage($chatId,'✅ <b>رزروهای انجام شده</b>',inlineKeyboard($rows));}
function reservationBookingDetailsText($r,$game){$u=getUser((int)$r['user_id']);$un=trim((string)($u['username']??''));$un=$un!==''?'@'.$un:'ندارد';[$total,$remaining]=reservationTotalAmount(['per_person_amount'=>$r['per_person_amount'],'amount'=>$r['amount']],(int)$r['players']);$status=$r['status']==='approved'?'✅ تأیید شده':($r['status']==='pending'?'⏳ در انتظار':'❌ رد شده');return "🎟 <b>اطلاعات کامل رزرو</b>\n\n🆔 شماره رزرو: <code>".escapeHtml($r['booking_code'])."</code>\n🎮 بازی: <b>".escapeHtml($game['name']??'')."</b>\n👤 نام و نام خانوادگی: <b>".escapeHtml($r['full_name']??'')."</b>\n🆔 آیدی عددی: <code>".(int)$r['user_id']."</code>\n🔗 یوزرنیم: <b>".escapeHtml($un)."</b>\n📅 تاریخ: <b>".escapeHtml(reservationDayLabel($r['slot_date']??''))."</b>\n🕐 سانس: <b>".substr((string)$r['slot_time'],0,5)."</b>\n👥 تعداد نفرات: <b>".(int)$r['players']."</b>\n👤 مبلغ هر نفر: <b>".number_format((float)$r['per_person_amount'],0,'.',',')." تومان</b>\n💰 مبلغ کل: <b>".number_format($total,0,'.',',')." تومان</b>\n💳 مبلغ پیش پرداخت: <b>".number_format((float)$r['amount'],0,'.',',')." تومان</b>\n💵 مانده پرداخت در محل: <b>".number_format($remaining,0,'.',',')." تومان</b>\n📌 وضعیت: <b>".$status."</b>";}
function reservationSendReceipt($chatId,$r){$fid=trim((string)($r['receipt_file_id']??''));if($fid===''){sendMessage($chatId,'❌ رسید این رزرو در دسترس نیست.');return false;}if(($r['receipt_type']??'')==='photo'){$res=sendPhoto($chatId,$fid,'🧾 رسید پرداخت رزرو <b>'.escapeHtml($r['booking_code']).'</b>');if(is_array($res)&&!empty($res['ok']))return true;}$res=sendDocument($chatId,$fid,'🧾 رسید پرداخت رزرو '.escapeHtml($r['booking_code']));if(is_array($res)&&!empty($res['ok']))return true;$res=sendPhoto($chatId,$fid,'🧾 رسید پرداخت رزرو <b>'.escapeHtml($r['booking_code']).'</b>');return is_array($res)&&!empty($res['ok']);}
function reservationSearchByCode($chatId,$code){
    if(!isOwner($chatId)&&!isFounder($chatId)){sendMessage($chatId,'⛔ این بخش فقط برای Owner و Founder است.',adminMenu($chatId));return true;}
    $code=strtoupper(trim((string)$code));
    if($code===''){sendMessage($chatId,'❌ کد رزرو را وارد کن.',cancelButton());return true;}
    $p=reservationDb();
    $q=$p->prepare('SELECT id FROM reservations WHERE booking_code=? LIMIT 1');
    $q->execute([$code]);
    $row=$q->fetch();
    if(!$row){sendMessage($chatId,'❌ رزروی با این کد پیدا نشد.');return true;}
    $r=reservationGet((int)$row['id']);
    if(!$r){sendMessage($chatId,'❌ اطلاعات این رزرو پیدا نشد.');return true;}
    $g=getGame((int)$r['game_id']);
    $status=$r['status']==='approved'?'✅ تأیید شده':($r['status']==='pending'?'⏳ در انتظار':'❌ رد شده');
    $txt=reservationBookingDetailsText($r,$g?:[]);
    $kb=[];
    if(!empty($r['receipt_file_id']))$kb[]=[['text'=>'🧾 دریافت رسید این رزرو','callback_data'=>'bookadmin:receipt:'.(int)$r['id']]];
    if($r['status']==='pending')$kb[]=[['text'=>'✅ تأیید','callback_data'=>'bookadmin:approve:'.(int)$r['id']],['text'=>'❌ رد','callback_data'=>'bookadmin:reject:'.(int)$r['id']]];
    $kb[]=[['text'=>'🔎 جستجوی یک رزرو دیگر','callback_data'=>'admin:reservation_search']];
    $kb[]=[['text'=>'↩️ رزرو رسیدی‌ها','callback_data'=>'admin:reservation_list']];
    sendMessage($chatId,'🔎 <b>نتیجه جستجوی رزرو</b>

'.$txt,inlineKeyboard($kb));
    return true;
}

function reservationAdminHandleState($message,$state){
    $chat=(int)$message['chat']['id'];$a=$state['action']??'';$text=cleanTelegramText($message['text']??'');
    if($a==='reservation_search_code'){if(!isOwner($chat)&&!isFounder($chat))return true;reservationSearchByCode($chat,$text);clearAdminState($chat);return true;}
    if(in_array($a,['reservation_card','reservation_holder'],true)){if(!isOwner($chat)&&!isFounder($chat))return true;$g=(int)$state['game_id'];if($a==='reservation_card')reservationSaveGameSettings($g,['card_number'=>$text]);else reservationSaveGameSettings($g,['card_holder'=>$text]);clearAdminState($chat);sendMessage($chat,'✅ ذخیره شد.',reservationAdminMenu($g,$chat));return true;}
    if(in_array($a,['reservation_slot_amount','reservation_slot_perperson'],true)){if(!reservationCanManage($chat,$state['game_id']))return true;$raw=str_replace([',','٬',' '],'',$text);if($raw===''||!is_numeric($raw)||$raw<0){sendMessage($chat,'❌ مبلغ نامعتبر است.');return true;}$okmsg=$a==='reservation_slot_amount'?reservationSetSlotAmount((int)$state['game_id'],(int)$state['weekday'],$state['slot_time'],(float)$raw):reservationSetSlotPerPerson((int)$state['game_id'],(int)$state['weekday'],$state['slot_time'],(float)$raw);clearAdminState($chat);sendMessage($chat,$okmsg[0]?'✅ '.$okmsg[1]:'❌ '.$okmsg[1],$okmsg[0]?reservationAdminSlotsMenu((int)$state['game_id'],(int)$state['weekday']):cancelButton());return true;}
    if($a==='reservation_reject_reason'){ $rid=(int)$state['reservation_id'];$r=reservationGet($rid);if(!$r||(!isOwner($chat)&&!isFounder($chat))){return true;}try{$old=reservationUpdateStatus($rid,'rejected',$chat,$text);sendMessage($old['user_id'],'❌ <b>رزرو شما رد شد.</b>\n\nدلیل:\n'.escapeHtml($text));clearAdminState($chat);sendMessage($chat,'✅ رزرو رد شد و سانس دوباره آزاد شد.',reservationAdminMenu($old['game_id'],$chat));}catch(Throwable $e){sendMessage($chat,'❌ '.escapeHtml($e->getMessage()));}return true;}
    if($a==='reservation_addslot'||$a==='reservation_addslot_all'){ $gid=(int)$state['game_id'];if(!reservationCanManage($chat,$gid))return true;if(!preg_match('/^([01]\d|2[0-3]):[0-5]\d$/',$text)){sendMessage($chat,'❌ فرمت ساعت: 17:00');return true;}$action=$a==='reservation_addslot_all'?'reservation_addslot_all_perperson':'reservation_addslot_perperson';$next=['action'=>$action,'game_id'=>$gid,'slot_time'=>substr($text,0,5)];if($a==='reservation_addslot')$next['weekday']=(int)$state['weekday'];saveUser($chat,['admin_reservation_temp'=>$next]);setAdminState($chat,$next);sendMessage($chat,'👤 <b>مبلغ هر نفر</b> را به تومان وارد کن:\n\n🕐 سانس: <b>'.substr($text,0,5).'</b>'.($a==='reservation_addslot'?'\n📅 روز: <b>'.reservationWeekdayName((int)$state['weekday']).'</b>':'\n📅 روزها: <b>شنبه تا جمعه</b>'),cancelButton());return true;}
    if($a==='reservation_addslot_perperson'||$a==='reservation_addslot_all_perperson'){ $gid=(int)$state['game_id'];if(!reservationCanManage($chat,$gid))return true;$raw=str_replace([',','٬',' '],'',$text);if($raw===''||!is_numeric($raw)||$raw<0){sendMessage($chat,'❌ مبلغ هر نفر نامعتبر است.');return true;}$next=['action'=>$a==='reservation_addslot_all_perperson'?'reservation_addslot_all_amount':'reservation_addslot_amount','game_id'=>$gid,'slot_time'=>$state['slot_time'],'per_person_amount'=>(float)$raw];if(isset($state['weekday']))$next['weekday']=(int)$state['weekday'];setAdminState($chat,$next);sendMessage($chat,'💳 <b>مبلغ پیش پرداخت این سانس</b> را به تومان وارد کن:\n\n🕐 سانس: <b>'.substr($state['slot_time'],0,5).'</b>\n👤 هر نفر: <b>'.number_format((float)$raw,0,'.',',').' تومان</b>',cancelButton());return true;}
    if($a==='reservation_addslot_amount'||$a==='reservation_addslot_all_amount'){ $gid=(int)$state['game_id'];if(!reservationCanManage($chat,$gid))return true;$raw=str_replace([',','٬',' '],'',$text);if($raw===''||!is_numeric($raw)||$raw<0){sendMessage($chat,'❌ مبلغ پیش پرداخت نامعتبر است.');return true;}try{if($a==='reservation_addslot_all_amount'){[$added,$skipped]=reservationAddSlotAllDays($gid,$state['slot_time'],(float)$state['per_person_amount'],(float)$raw);clearAdminState($chat);$names=array_map('reservationWeekdayName',$added);$msg='✅ سانس <b>'.substr($state['slot_time'],0,5).'</b> برای <b>همه روزهای هفته</b> اضافه شد.\n👤 هر نفر: <b>'.number_format((float)$state['per_person_amount'],0,'.',',').' تومان</b>\n💳 پیش‌پرداخت: <b>'.number_format((float)$raw,0,'.',',').' تومان</b>';if($skipped)$msg.='\n⚠️ در '.count($skipped).' روز این ساعت از قبل وجود داشت.';sendMessage($chat,$msg,reservationAdminSlotsMenu($gid,0));return true;}[$ok,$msg]=reservationAddSlot($gid,(int)$state['weekday'],$state['slot_time'],(float)$state['per_person_amount'],(float)$raw);if(!$ok){sendMessage($chat,'❌ این ساعت قبلاً برای این روز وجود دارد.',reservationAdminSlotsMenu($gid,(int)$state['weekday']));return true;}clearAdminState($chat);sendMessage($chat,'✅ سانس <b>'.substr($state['slot_time'],0,5).'</b> برای <b>'.reservationWeekdayName((int)$state['weekday']).'</b> ذخیره شد.\n👤 هر نفر: <b>'.number_format((float)$state['per_person_amount'],0,'.',',').' تومان</b>\n💳 پیش‌پرداخت: <b>'.number_format((float)$raw,0,'.',',').' تومان</b>',reservationAdminSlotsMenu($gid,(int)$state['weekday']));}catch(Throwable $e){sendMessage($chat,'❌ خطا در ذخیره سانس: '.escapeHtml($e->getMessage()));}return true;}return false;
}
