<?php
namespace App\Repositories;

use App\Database\Connection;
use App\Utils\DateNormalizer;
use PDO;

class KpiRepository
{
    private $db;
    private $uiOracleCache = [];
    private static $activityTypeBackfillAttempted = false;

    public function __construct(Connection $db)
    {
        $this->db = $db;
    }

    private function dateRangeFromFilters(array $filters): array
    {
        $fallback = DateNormalizer::currentJalaliMonthRange();
        $dateFrom = !empty($filters['date_from']) ? (string)$filters['date_from'] : (string)$fallback['date_from'];
        $dateTo = !empty($filters['date_to']) ? (string)$filters['date_to'] : (string)$fallback['date_to'];

        return [$dateFrom, $dateTo];
    }

    /**
     * 1. فروش
     * تعداد معاملاتی که در بازه زمانی ساخته شده‌اند
     * مجموع مبلغ قابل پرداخت این معاملات
     * مجموع کل پرداخت‌های ثبت شده برای این معاملات (بدون محدودیت تاریخی)
     */
    public function getSales(array $filters): array
    {
        if ($this->getKpiPaymentDateBasis($filters, 'sales') === 'ui_payment_date') {
            return $this->getSalesUiOracle($filters);
        }
        $pdo = $this->db->getPdo();
        $parts = [];
        $params = [];
        $statusCsv = $this->normalizeCsvList($filters['deal_statuses_csv'] ?? '');
        $leadTypeCsv = $this->normalizeCsvList($filters['deal_lead_types_csv'] ?? '');

        $legacyClause = $this->buildLegacyDealFilterClause($filters, 'd');
        if ($legacyClause !== '') {
            $parts[] = $legacyClause;
        }

        // فروش کل بر اساس تاریخ پرداخت (نه تاریخ ثبت معامله)
        if (!empty($filters['date_from'])) {
            $parts[] = "pay.payment_date >= ?";
            $params[] = $filters['date_from'];
        }
        if (!empty($filters['date_to'])) {
            $parts[] = "pay.payment_date <= ?";
            $params[] = $filters['date_to'];
        }

        if (!empty($filters['owner_didar_ids_csv'])) {
            $parts[] = "FIND_IN_SET(d.owner_didar_id, ?)";
            $params[] = $filters['owner_didar_ids_csv'];
        } elseif (!empty($filters['owner_didar_id']) && trim((string)$filters['owner_didar_id']) !== '') {
            $parts[] = "d.owner_didar_id = ?";
            $params[] = trim((string)$filters['owner_didar_id']);
        }

        if (!empty($filters['product_ids_csv'])) {
            $parts[] = "EXISTS (SELECT 1 FROM deal_products dp WHERE dp.deal_id = d.id AND FIND_IN_SET(dp.product_id, ?))";
            $params[] = $filters['product_ids_csv'];
        } elseif (!empty($filters['product_id'])) {
            $parts[] = "EXISTS (SELECT 1 FROM deal_products dp WHERE dp.deal_id = d.id AND dp.product_id = ?)";
            $params[] = $filters['product_id'];
        }

        if ($statusCsv !== '' && !$this->csvHasAll($statusCsv, ['pending', 'won', 'lost'])) {
            $parts[] = "(
                (FIND_IN_SET('pending', ?) > 0 AND d.status NOT IN ('Won', 'Lost'))
                OR (FIND_IN_SET('won', ?) > 0 AND d.status = 'Won')
                OR (FIND_IN_SET('lost', ?) > 0 AND d.status = 'Lost')
            )";
            $params[] = $statusCsv;
            $params[] = $statusCsv;
            $params[] = $statusCsv;
        }

        if ($leadTypeCsv !== '' && !$this->csvHasAll($leadTypeCsv, ['new', 'old'])) {
            $parts[] = "(
                (FIND_IN_SET('new', ?) > 0 AND EXISTS (
                    SELECT 1 FROM persons pflt
                    WHERE pflt.didar_contact_id = d.contact_didar_id
                      AND DATE(COALESCE(pflt.register_time, pflt.created_at)) = DATE(COALESCE(d.register_time, d.created_at))
                ))
                OR (FIND_IN_SET('old', ?) > 0 AND EXISTS (
                    SELECT 1 FROM persons pflt
                    WHERE pflt.didar_contact_id = d.contact_didar_id
                      AND DATE(COALESCE(pflt.register_time, pflt.created_at)) < DATE(COALESCE(d.register_time, d.created_at))
                ))
            )";
            $params[] = $leadTypeCsv;
            $params[] = $leadTypeCsv;
        }

        $where = !empty($parts) ? implode(' AND ', $parts) : '1=1';
        $salesQualifierSql = $this->buildKpiFirstPaymentDayByDealSql($filters, 'sales');
        $salesAllTimeTotalsSql = $this->buildKpiAllTimePaymentTotalsByDealSql($filters, 'sales');

        $sql = "
            SELECT
                COUNT(*) as deal_count,
                SUM(s.payable_amount) as total_contract_amount,
                SUM(s.collected_amount) as total_collected_amount
            FROM (
                SELECT
                    d.id,
                    COALESCE(d.payable_amount, 0) as payable_amount,
                    SUM(COALESCE(pay.amount, 0)) as collected_amount
                FROM deals d
                INNER JOIN ({$salesQualifierSql}) pay ON pay.deal_id = d.id
                LEFT JOIN ({$salesAllTimeTotalsSql}) ap ON ap.deal_id = d.id
                WHERE {$where}
                  AND pay.payment_date >= DATE(COALESCE(d.register_time, d.created_at))
                GROUP BY d.id, d.payable_amount
            ) s
        ";

        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    /**
     * 2. تسویه حساب کامل
     * تعداد هر معامله که حداقل یک پرداخت ثبت شده دارد
     * و مجموع مبلغ پرداخت های ثبت شده آن با مبلغ قابل پرداخت آن معامله (name="payable_amount") برابر باشد
     * و مجموع مبلغ تسویه شده این معاملات تسویه شده
     */
    public function getSettledDeals(array $filters): array
    {
        $pdo = $this->db->getPdo();
        $whereClause = $this->buildWhereClauseForDeals($filters, 'd');
        $params = $this->buildParamsForDeals($filters);
        $paymentsByDealSql = $this->buildKpiAllTimePaymentTotalsByDealSql($filters, 'payment');

        $sql = "
            SELECT
                COUNT(DISTINCT d.id) as deal_count,
                SUM(d.payable_amount) as total_amount,
                SUM(COALESCE(tp.total_paid, 0)) as total_collected_amount
            FROM deals d
            LEFT JOIN ({$paymentsByDealSql}) tp ON tp.deal_id = d.id
            WHERE d.payable_amount IS NOT NULL
              AND d.payable_amount > 0
              AND COALESCE(tp.total_paid, 0) >= d.payable_amount
              AND {$whereClause}
        ";

        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    /**
     * 3. مانده حساب
     * تعداد معاملاتی که حداقل یک پرداخت ثبت شده دارد
     * و مجموع رقم پرداخت های ثبت شده آن با مبلغ قابل پرداخت آن معامله برابر نباشد
     * و مجموع مبلغ پرداخت شده این معاملات
     * و مجموع مبلغ پرداخت نشده این معاملات
     * بر اساس تفریق مبلغ قابل پرداخت از مجموع پرداخت های ثبت شده
     */
    public function getOutstandingBalance(array $filters): array
    {
        $pdo = $this->db->getPdo();
        $where = $this->buildWhereClauseForDeals($filters, 'd');
        $params = $this->buildParamsForDeals($filters);
        $paymentsByDealSql = $this->buildKpiAllTimePaymentTotalsByDealSql($filters, 'payment');

        $sql = "
            SELECT
                COUNT(DISTINCT d.id) as deal_count,
                SUM(d.payable_amount) as total_contract_amount,
                SUM(COALESCE(tp.total_paid, 0)) as total_collected,
                SUM(d.payable_amount - COALESCE(tp.total_paid, 0)) as outstanding_amount
            FROM deals d
            LEFT JOIN ({$paymentsByDealSql}) tp ON tp.deal_id = d.id
            WHERE d.payable_amount IS NOT NULL
              AND d.payable_amount > 0
              AND COALESCE(tp.total_paid, 0) > 0
              AND COALESCE(tp.total_paid, 0) < d.payable_amount
              AND {$where}
        ";

        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    /**
     * 4. فروش ناموفق
     * تعداد معاملاتی که در وضعیت ناموفق قرار دارند
     * و مجموع مبلغ قابل پرداخت معاملات ناموفق
     * همچنین دلایل ناموفق شدن معاملات باید تعداد هر دلیل مشخص باشه
     * قابل تفکیک باشد
     */
    public function getLostSales(array $filters): array
    {
        $pdo = $this->db->getPdo();
        $where = $this->buildWhereClauseForLostSales($filters);
        $params = $this->buildParamsForLostSales($filters);

        $sql = "
            SELECT
                COUNT(*) as total_lost,
                s.failure_reason_code,
                s.failure_reason_text,
                SUM(s.payable_amount) as total_amount
            FROM (
                SELECT
                    d.id,
                    d.payable_amount,
                    d.failure_reason_code,
                    COALESCE(
                        NULLIF(TRIM(d.failure_reason_text), ''),
                        NULLIF(TRIM(d.failure_reason_description), ''),
                        CASE TRIM(d.failure_reason_code)
                            WHEN 'no_financial_ability' THEN 'عدم توان مالی'
                            WHEN 'no_purchase_intent' THEN 'فعلا قصد خرید ندارد'
                            WHEN 'competitor_purchase' THEN 'خرید از رقبا'
                            WHEN 'wrong_number' THEN 'شماره اشتباه'
                            WHEN 'irrelevant_lead' THEN 'لید نامرتبط'
                            WHEN 'other' THEN 'سایر'
                            ELSE NULL
                        END,
                        NULLIF(TRIM(d.failure_reason_code), ''),
                        'نامشخص'
                    ) as failure_reason_text
                FROM deals d
                WHERE d.status = 'Lost'
                  AND {$where}
            ) s
            GROUP BY s.failure_reason_code, s.failure_reason_text
        ";

        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    /**
     * 5. فروش جاری
     * تعداد و مجموع مبلغ قابل پرداخت همه معاملاتی که در وضعیت جاری قرار دارند
     */
    public function getOpenPipeline(array $filters): array
    {
        $pdo = $this->db->getPdo();
        $where = $this->buildWhereClauseForDeals($filters, 'd');
        $params = $this->buildParamsForDeals($filters);

        $sql = "
            SELECT
                COUNT(DISTINCT d.id) as deal_count,
                SUM(d.payable_amount) as total_potential_amount
            FROM deals d
            WHERE d.status NOT IN ('Won', 'Lost')
              AND {$where}
        ";

        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    /**
     * 5.5. معاملات بدون پرداخت
     * تعداد معاملاتی که هیچ transaction ندارند
     * شامل معاملات با payable_amount > 0 و همچنین معاملات با payable_amount = 0 یا NULL
     */
    public function getDealsWithoutPayments(array $filters): array
    {
        $pdo = $this->db->getPdo();
        $where = $this->buildWhereClauseForDeals($filters, 'd');
        $params = $this->buildParamsForDeals($filters);
        $paymentsByDealSql = $this->buildAbsolutePaymentPresenceByDealSql();

        $sql = "
            SELECT
                COUNT(DISTINCT d.id) as deal_count,
                SUM(COALESCE(d.payable_amount, 0)) as total_potential_amount
            FROM deals d
            LEFT JOIN ({$paymentsByDealSql}) tp ON tp.deal_id = d.id
            WHERE d.status NOT IN ('Won', 'Lost')
              AND (tp.total_paid IS NULL OR tp.total_paid = 0)
              AND {$where}
        ";

        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    /**
     * تعداد فروش محصولاتی که حداقل یک پرداخت در بازه زمانی دارند
     */
    public function getProductSalesCounts(array $filters): array
    {
        $pdo = $this->db->getPdo();
        $parts = [];
        $params = [];
        $dealProductHintExpr = $this->buildDealProductHintExpr('d');
        $resolvedProductExpr = $this->buildResolvedProductNameExpr($dealProductHintExpr, 'p', 'dp');
        $primaryDealProductSql = $this->buildPrimaryDealProductSql();

        $legacyClause = $this->buildLegacyDealFilterClause($filters, 'd');
        if ($legacyClause !== '') {
            $parts[] = $legacyClause;
        }
        if (!empty($filters['owner_didar_ids_csv'])) {
            $parts[] = "FIND_IN_SET(d.owner_didar_id, ?)";
            $params[] = $filters['owner_didar_ids_csv'];
        } elseif (!empty($filters['owner_didar_id']) && trim($filters['owner_didar_id']) !== '') {
            $parts[] = "d.owner_didar_id = ?";
            $params[] = trim($filters['owner_didar_id']);
        }
        if (!empty($filters['product_ids_csv'])) {
            $parts[] = "FIND_IN_SET(dp.product_id, ?)";
            $params[] = $filters['product_ids_csv'];
        } elseif (!empty($filters['product_id'])) {
            $parts[] = "dp.product_id = ?";
            $params[] = $filters['product_id'];
        }

        $paymentsByDealDateSql = $this->buildKpiPaymentsWindowByDealDateSql($filters, 'sales');
        $paymentDateWhere = [];
        $paymentParams = [];
        if (!empty($filters['date_from'])) {
            $paymentDateWhere[] = "pbd.payment_date >= ?";
            $paymentParams[] = $filters['date_from'];
        }
        if (!empty($filters['date_to'])) {
            $paymentDateWhere[] = "pbd.payment_date <= ?";
            $paymentParams[] = $filters['date_to'];
        }
        $paymentDateClause = !empty($paymentDateWhere) ? implode(' AND ', $paymentDateWhere) : '1=1';
        $where = !empty($parts) ? implode(' AND ', $parts) : '1=1';

        $sql = "
            SELECT
                MIN(dp.product_id) AS product_id,
                {$resolvedProductExpr} AS product_name,
                COUNT(DISTINCT d.id) AS deal_count
            FROM deals d
            INNER JOIN (
                SELECT DISTINCT pbd.deal_id
                FROM ({$paymentsByDealDateSql}) pbd
                WHERE {$paymentDateClause}
            ) pay ON pay.deal_id = d.id
            INNER JOIN ({$primaryDealProductSql}) dp ON dp.deal_id = d.id
            LEFT JOIN products p ON p.id = dp.product_id
            WHERE {$where}
            GROUP BY {$resolvedProductExpr}
            ORDER BY deal_count DESC, product_name ASC
        ";

        $stmt = $pdo->prepare($sql);
        $stmt->execute(array_merge($paymentParams, $params));
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    /**
     * 6. نرخ تبدیل و نرخ عدم تبدیل لید جدید
     * تعداد معامله ثبت لید های جدید (یعنی لیدی که در آن بازه تاریخی برای اولین بار در دیتابیس ثبت شده)
     * برای هر کاربر
     * به همراه نرخ عدم تبدیل برای همین معاملات و لیدها بر اساس معاملاتی که هنوز براشون پرداختی ثبت نشده
     * 
     * SIMPLIFIED VERSION: Uses person.register_time as basis for "new lead" definition
     */
    public function getDealConversionRate(array $filters): array
    {
        if ($this->getKpiPaymentDateBasis($filters, 'sales') === 'ui_payment_date') {
            return $this->getDealConversionRateUiOracle($filters);
        }
        $pdo = $this->db->getPdo();
        $where = $this->buildWhereClauseForDeals($filters, 'd');
        $params = $this->buildParamsForDeals($filters);
        $paymentsByDealSql = $this->buildKpiPaymentsByDealSql($filters, 'sales');
        $firstPaymentAllTimeFilters = $filters;
        unset($firstPaymentAllTimeFilters['date_from'], $firstPaymentAllTimeFilters['date_to']);
        $firstPaymentAllTimeFilters['kpi_sales_count_rule'] = 'first_payment_only';
        $firstPaymentAllTimeSql = $this->buildKpiPaymentsByDealSql($firstPaymentAllTimeFilters, 'sales', 'first_payment_only');

        $sql = "
            SELECT
                COUNT(DISTINCT d.id) as total_deals,
                COUNT(DISTINCT CASE
                    WHEN COALESCE(tp.total_paid, 0) > 0
                     AND (
                        fpa.first_payment_date IS NULL
                        OR fpa.first_payment_date >= DATE(COALESCE(d.register_time, d.created_at))
                     )
                    THEN d.id
                END) as converted_deals
            FROM deals d
            LEFT JOIN ({$paymentsByDealSql}) tp ON tp.deal_id = d.id
            LEFT JOIN ({$firstPaymentAllTimeSql}) fpa ON fpa.deal_id = d.id
            WHERE {$where}
              AND (
                fpa.first_payment_date IS NULL
                OR fpa.first_payment_date >= DATE(COALESCE(d.register_time, d.created_at))
              )
        ";

        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        $row = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];

        $totalDeals = (int)($row['total_deals'] ?? 0);
        $convertedDeals = (int)($row['converted_deals'] ?? 0);
        $conversionRate = $totalDeals > 0 ? round(($convertedDeals / $totalDeals) * 100, 2) : 0;
        $nonConversionRate = $totalDeals > 0 ? round((($totalDeals - $convertedDeals) / $totalDeals) * 100, 2) : 0;

        return [
            'total_deals' => $totalDeals,
            'converted_deals' => $convertedDeals,
            'conversion_rate' => $conversionRate,
            'non_conversion_rate' => $nonConversionRate
        ];
    }

    /**
     * نرخ تبدیل لید جدید (persons)
     * لید جدید: persons.register_time در بازه
     * تبدیل: حداقل یک معامله با پرداخت confirmed (بدون محدودیت تاریخ پرداخت)
     */
    public function getLeadConversionRate(array $filters): array
    {
        $pdo = $this->db->getPdo();
        $paymentsByDealSql = $this->buildKpiPaymentsByDealSql($filters, 'sales');

        $dateFrom = $filters['date_from'] ?? null;
        $dateTo = $filters['date_to'] ?? null;

        $personWhereParts = [];
        $personParams = [];

        if (!empty($dateFrom)) {
            $personWhereParts[] = "DATE(p.register_time) >= ?";
            $personParams[] = $dateFrom;
        }
        if (!empty($dateTo)) {
            $personWhereParts[] = "DATE(p.register_time) <= ?";
            $personParams[] = $dateTo;
        }
        if (!empty($filters['owner_didar_ids_csv'])) {
            $personWhereParts[] = "FIND_IN_SET(p.owner_didar_id, ?)";
            $personParams[] = $filters['owner_didar_ids_csv'];
        } elseif (!empty($filters['owner_didar_id'])) {
            $personWhereParts[] = "p.owner_didar_id = ?";
            $personParams[] = $filters['owner_didar_id'];
        }

        $personWhere = !empty($personWhereParts) ? implode(' AND ', $personWhereParts) : '1=1';

        $leadsSql = "
            SELECT COUNT(DISTINCT p.didar_contact_id) as total_new_leads
            FROM persons p
            WHERE {$personWhere}
        ";
        $stmt = $pdo->prepare($leadsSql);
        $stmt->execute($personParams);
        $totalNewLeads = (int) $stmt->fetchColumn();

        $convertedWhereParts = $personWhereParts;
        $convertedParams = $personParams;

        if (!empty($filters['owner_didar_ids_csv'])) {
            $convertedWhereParts[] = "FIND_IN_SET(d.owner_didar_id, ?)";
            $convertedParams[] = $filters['owner_didar_ids_csv'];
        } elseif (!empty($filters['owner_didar_id'])) {
            $convertedWhereParts[] = "d.owner_didar_id = ?";
            $convertedParams[] = $filters['owner_didar_id'];
        }
        if (!empty($filters['product_ids_csv'])) {
            $convertedWhereParts[] = "EXISTS (SELECT 1 FROM deal_products dp WHERE dp.deal_id = d.id AND FIND_IN_SET(dp.product_id, ?))";
            $convertedParams[] = $filters['product_ids_csv'];
        } elseif (!empty($filters['product_id'])) {
            $convertedWhereParts[] = "EXISTS (SELECT 1 FROM deal_products dp WHERE dp.deal_id = d.id AND dp.product_id = ?)";
            $convertedParams[] = $filters['product_id'];
        }

        $convertedWhere = !empty($convertedWhereParts) ? implode(' AND ', $convertedWhereParts) : '1=1';

        $convertedSql = "
            SELECT COUNT(DISTINCT p.didar_contact_id) as converted_leads
            FROM persons p
            INNER JOIN deals d ON d.contact_didar_id = p.didar_contact_id
            INNER JOIN ({$paymentsByDealSql}) tp ON tp.deal_id = d.id
            WHERE COALESCE(tp.total_paid, 0) > 0
              AND {$convertedWhere}
        ";
        $stmt = $pdo->prepare($convertedSql);
        $stmt->execute($convertedParams);
        $convertedLeads = (int) $stmt->fetchColumn();

        $conversionRate = $totalNewLeads > 0 ? round(($convertedLeads / $totalNewLeads) * 100, 2) : 0;
        $nonConversionRate = $totalNewLeads > 0 ? round((($totalNewLeads - $convertedLeads) / $totalNewLeads) * 100, 2) : 0;

        return [
            'total_new_leads' => $totalNewLeads,
            'converted_leads' => $convertedLeads,
            'conversion_rate' => $conversionRate,
            'non_conversion_rate' => $nonConversionRate
        ];
    }

    /**
     * Backward compatibility
     */
    public function getNewLeadConversionRate(array $filters): array
    {
        return $this->getLeadConversionRate($filters);
    }

    /**
     * 7. نرخ زمان تبدیل
     * نرخ مدت زمانی که طول کشیده از زمان ثبت یک معامله تا زمان تبدیل شدن آن معامله
     * یعنی از created_at تا اولین پرداخت
     * 
     * FIXED: Now calculates time from FIRST PAYMENT DATE to FIRST PAYMENT DATE (always 0)
     * This correctly represents that conversion happens at registration time (when first payment is received)
     */
    public function getTimeToConversion(array $filters): array
    {
        if ($this->getKpiPaymentDateBasis($filters, 'sales') === 'ui_payment_date') {
            $oracle = $this->getUiOracleKpiData($filters);
            $deals = (array)($oracle['state']['deals'] ?? []);
            $convCohort = (array)($oracle['sets']['conversion_cohort'] ?? []);
            $convConverted = (array)($oracle['sets']['conversion_converted'] ?? []);
            $salesFirstAll = (array)($oracle['aggs']['sales_first_all'] ?? []);

            $sumDays = 0.0;
            $countDays = 0;
            foreach ($convCohort as $dealId => $_) {
                $dealId = (string)$dealId;
                if (!isset($convConverted[$dealId])) {
                    continue;
                }
                $deal = $deals[$dealId] ?? null;
                if (!$deal) {
                    continue;
                }
                $dealDate = (string)($deal['deal_date'] ?? '');
                $firstDate = (string)($salesFirstAll[$dealId]['first'] ?? '');
                if ($dealDate === '' || $firstDate === '') {
                    continue;
                }
                try {
                    $diffDays = (int)((new \DateTime($firstDate))->diff(new \DateTime($dealDate)))->format('%r%a');
                } catch (\Throwable $e) {
                    continue;
                }
                if ($diffDays < 0) {
                    continue;
                }
                $sumDays += $diffDays;
                $countDays++;
            }

            return [
                'deal_count' => $countDays,
                'avg_conversion_days' => $countDays > 0 ? round($sumDays / $countDays, 2) : 0.0,
            ];
        }

        $pdo = $this->db->getPdo();
        $where = $this->buildWhereClauseForDeals($filters, 'd');
        $params = $this->buildParamsForDeals($filters);
        $firstPaymentAllTimeFilters = $filters;
        unset($firstPaymentAllTimeFilters['date_from'], $firstPaymentAllTimeFilters['date_to']);
        $firstPaymentAllTimeFilters['kpi_sales_count_rule'] = 'first_payment_only';
        $firstPaymentAllTimeSql = $this->buildKpiPaymentsByDealSql($firstPaymentAllTimeFilters, 'sales', 'first_payment_only');

        $sql = "
            SELECT
                COUNT(*) as deal_count,
                AVG(DATEDIFF(first_payment_date, deal_date)) as avg_conversion_days
            FROM (
                SELECT
                    d.id,
                    DATE(COALESCE(d.register_time, d.created_at)) as deal_date,
                    pay.first_payment_date
                FROM deals d
                INNER JOIN ({$firstPaymentAllTimeSql}) pay ON pay.deal_id = d.id
                WHERE COALESCE(pay.total_paid, 0) > 0
                  AND {$where}
                GROUP BY d.id
            ) s
            WHERE s.first_payment_date IS NOT NULL
              AND s.first_payment_date >= s.deal_date
        ";

        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    /**
     * فروش روزانه در ماه جاری
     */
    public function getDailySales(array $filters): array
    {
        if ($this->getKpiPaymentDateBasis($filters, 'sales') === 'ui_payment_date') {
            return $this->getDailySalesUiOracle($filters);
        }

        $pdo = $this->db->getPdo();
        $parts = [];
        $params = [];

        $legacyClause = $this->buildLegacyDealFilterClause($filters, 'd');
        if ($legacyClause !== '') {
            $parts[] = $legacyClause;
        }
        if (!empty($filters['date_from'])) {
            $parts[] = "pay.payment_date >= ?";
            $params[] = $filters['date_from'];
        }
        if (!empty($filters['date_to'])) {
            $parts[] = "pay.payment_date <= ?";
            $params[] = $filters['date_to'];
        }
        if (!empty($filters['owner_didar_ids_csv'])) {
            $parts[] = "FIND_IN_SET(d.owner_didar_id, ?)";
            $params[] = $filters['owner_didar_ids_csv'];
        } elseif (!empty($filters['owner_didar_id']) && trim($filters['owner_didar_id']) !== '') {
            $parts[] = "d.owner_didar_id = ?";
            $params[] = trim($filters['owner_didar_id']);
        }
        if (!empty($filters['product_ids_csv'])) {
            $parts[] = "EXISTS (SELECT 1 FROM deal_products dp WHERE dp.deal_id = d.id AND FIND_IN_SET(dp.product_id, ?))";
            $params[] = $filters['product_ids_csv'];
        } elseif (!empty($filters['product_id'])) {
            $parts[] = "EXISTS (SELECT 1 FROM deal_products dp WHERE dp.deal_id = d.id AND dp.product_id = ?)";
            $params[] = $filters['product_id'];
        }

        $where = !empty($parts) ? implode(' AND ', $parts) : '1=1';
        $paymentsByDealDateSql = $this->buildKpiPaymentsWindowByDealDateSql($filters, 'sales');

        $sql = "
            SELECT
                pay.payment_date as date,
                COUNT(DISTINCT d.id) as deal_count,
                SUM(d.payable_amount) as total_amount,
                SUM(pay.amount) as collected_amount
            FROM deals d
            INNER JOIN ({$paymentsByDealDateSql}) pay ON pay.deal_id = d.id
            WHERE {$where}
            GROUP BY pay.payment_date
            ORDER BY pay.payment_date
        ";

        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    private function getDailySalesUiOracle(array $filters): array
    {
        $oracle = $this->getUiOracleKpiData($filters);
        $rows = [];
        foreach (($oracle['sets']['sales_card_qualified'] ?? []) as $dealId => $_) {
            $deal = $oracle['state']['deals'][(string)$dealId] ?? null;
            if (!$deal) {
                continue;
            }
            $date = (string)($oracle['aggs']['sales_first_all'][(string)$dealId]['first'] ?? '');
            if ($date === '') {
                continue;
            }
            if (!isset($rows[$date])) {
                $rows[$date] = [
                    'date' => $date,
                    'deal_count' => 0,
                    'total_amount' => 0.0,
                    'collected_amount' => 0.0,
                ];
            }
            $rows[$date]['deal_count']++;
            $rows[$date]['total_amount'] += (float)($deal['payable_amount'] ?? 0);
            $rows[$date]['collected_amount'] += (float)($oracle['aggs']['sales_window'][(string)$dealId]['total'] ?? 0);
        }
        ksort($rows);
        return array_values($rows);
    }

    /**
     * فعالیت روزانه به تفکیک نوع فعالیت (برای نمودار Stacked)
     */
    private function buildResolvedActivityTypeExpr(string $alias = 'a'): string
    {
        return "
            COALESCE(
                NULLIF(
                    TRIM(
                        CASE
                            WHEN TRIM(COALESCE({$alias}.activity_type_title, '')) = 'فعالیت' THEN ''
                            ELSE COALESCE({$alias}.activity_type_title, '')
                        END
                    ),
                    ''
                ),
                NULLIF(TRIM(JSON_UNQUOTE(JSON_EXTRACT({$alias}.raw_json, '$.ActivityTypeTitle'))), ''),
                NULLIF(TRIM(JSON_UNQUOTE(JSON_EXTRACT({$alias}.raw_json, '$.title'))), ''),
                NULLIF(TRIM(JSON_UNQUOTE(JSON_EXTRACT({$alias}.raw_json, '$.title_alt'))), ''),
                NULLIF(TRIM(COALESCE({$alias}.stage, '')), ''),
                'نامشخص'
            )
        ";
    }

    private function buildCanonicalActivityTypeExpr(string $alias = 'a'): string
    {
        $resolvedExpr = $this->buildResolvedActivityTypeExpr($alias);
        return "
            CASE
                WHEN LOWER({$resolvedExpr}) IN ('contact_no_answer', 'no_answer', 'contact_not_answered') THEN 'تماس عدم پاسخ'
                WHEN LOWER({$resolvedExpr}) IN ('contact_followup', 'followup', 'contact_new', 'new_contact') THEN 'تماس پیگیری'
                WHEN LOWER({$resolvedExpr}) IN ('payment_link', 'send_payment_link') THEN 'ارسال لینک پرداخت'
                WHEN LOWER({$resolvedExpr}) IN ('outstanding_followup', 'outstanding_balance_followup', 'payment_followup') THEN 'پیگیری مانده حساب'
                WHEN LOWER({$resolvedExpr}) IN ('payment', 'paid', 'successful_payment') THEN 'پرداخت'
                WHEN LOWER({$resolvedExpr}) IN ('course_delivered') THEN 'برگزاری دوره'
                WHEN LOWER({$resolvedExpr}) IN ('without_purchase') THEN 'ناموفق'
                WHEN ({$resolvedExpr}) LIKE '%عدم پاسخ%' OR ({$resolvedExpr}) LIKE '%بی پاسخ%' THEN 'تماس عدم پاسخ'
                WHEN ({$resolvedExpr}) LIKE '%پیگیری مانده%' THEN 'پیگیری مانده حساب'
                WHEN ({$resolvedExpr}) LIKE '%ارسال لینک%' THEN 'ارسال لینک پرداخت'
                WHEN ({$resolvedExpr}) LIKE '%برگزاری%' OR ({$resolvedExpr}) LIKE '%دوره%' THEN 'برگزاری دوره'
                WHEN ({$resolvedExpr}) LIKE '%پشتیبانی%' THEN 'پشتیبانی'
                WHEN ({$resolvedExpr}) LIKE '%ارجاع%' OR LOWER({$resolvedExpr}) LIKE '%crm%' THEN 'ارجاع به CRM'
                WHEN ({$resolvedExpr}) LIKE '%ناموفق%' THEN 'ناموفق'
                WHEN ({$resolvedExpr}) LIKE '%پرداخت%' OR ({$resolvedExpr}) LIKE '%واریز%' THEN 'پرداخت'
                WHEN ({$resolvedExpr}) LIKE '%پیگیری%' OR ({$resolvedExpr}) LIKE '%تماس%' OR ({$resolvedExpr}) LIKE '%خروجی%' THEN 'تماس پیگیری'
                ELSE {$resolvedExpr}
            END
        ";
    }

    private function decodeActivityTypeTitleFromRawJson(string $rawJson): string
    {
        $rawJson = trim($rawJson);
        if ($rawJson === '') {
            return '';
        }
        $decoded = json_decode($rawJson, true);
        if (!is_array($decoded)) {
            return '';
        }
        $title = trim((string)($decoded['ActivityTypeTitle'] ?? ''));
        if ($title !== '') {
            return $title;
        }
        $title = trim((string)($decoded['title'] ?? ''));
        if ($title !== '' && $title !== 'فعالیت') {
            return $title;
        }
        $title = trim((string)($decoded['title_alt'] ?? ''));
        if ($title !== '' && $title !== 'فعالیت') {
            return $title;
        }
        if (isset($decoded['Activity']) && is_array($decoded['Activity'])) {
            $activityTitle = trim((string)($decoded['Activity']['ActivityTypeTitle'] ?? ''));
            if ($activityTitle !== '') {
                return $activityTitle;
            }
        }
        return '';
    }

    private function normalizeActivityTypeLabelPhp(string $value): string
    {
        $value = trim($value);
        $code = strtolower(str_replace([' ', '-', '/'], '_', $value));
        if ($value === '' || $value === 'فعالیت') {
            return 'نامشخص';
        }
        if (in_array($code, ['contact_no_answer', 'no_answer', 'contact_not_answered'], true)) {
            return 'تماس عدم پاسخ';
        }
        if (in_array($code, ['contact_followup', 'followup', 'contact_new', 'new_contact'], true)) {
            return 'تماس پیگیری';
        }
        if (in_array($code, ['payment_link', 'send_payment_link'], true)) {
            return 'ارسال لینک پرداخت';
        }
        if (in_array($code, ['outstanding_followup', 'outstanding_balance_followup', 'payment_followup'], true)) {
            return 'پیگیری مانده حساب';
        }
        if (in_array($code, ['payment', 'paid', 'successful_payment'], true)) {
            return 'پرداخت';
        }
        if (in_array($code, ['course_delivered'], true)) {
            return 'برگزاری دوره';
        }
        if (in_array($code, ['without_purchase'], true)) {
            return 'ناموفق';
        }
        if (mb_stripos($value, 'عدم پاسخ') !== false || mb_stripos($value, 'بی پاسخ') !== false) {
            return 'تماس عدم پاسخ';
        }
        if (mb_stripos($value, 'پیگیری مانده') !== false) {
            return 'پیگیری مانده حساب';
        }
        if (mb_stripos($value, 'ارسال لینک') !== false) {
            return 'ارسال لینک پرداخت';
        }
        if (mb_stripos($value, 'برگزاری') !== false || mb_stripos($value, 'دوره') !== false) {
            return 'برگزاری دوره';
        }
        if (mb_stripos($value, 'پشتیبانی') !== false) {
            return 'پشتیبانی';
        }
        if (mb_stripos($value, 'ارجاع') !== false || mb_stripos($value, 'crm') !== false) {
            return 'ارجاع به CRM';
        }
        if (mb_stripos($value, 'ناموفق') !== false) {
            return 'ناموفق';
        }
        if (mb_stripos($value, 'پرداخت') !== false || mb_stripos($value, 'واریز') !== false) {
            return 'پرداخت';
        }
        if (mb_stripos($value, 'پیگیری') !== false || mb_stripos($value, 'تماس') !== false || mb_stripos($value, 'خروجی') !== false) {
            return 'تماس پیگیری';
        }
        return $value;
    }

    public function ensureActivityTypeBackfill(): void
    {
        if (self::$activityTypeBackfillAttempted) {
            return;
        }
        self::$activityTypeBackfillAttempted = true;

        $pdo = $this->db->getPdo();
        $rows = $this->runSelectAll("
            SELECT
                a.id,
                COALESCE(a.activity_type_id, '') as activity_type_id,
                COALESCE(a.activity_type_title, '') as activity_type_title,
                COALESCE(a.raw_json, '') as raw_json,
                COALESCE(a.stage, '') as stage
            FROM activities a
            WHERE TRIM(COALESCE(a.activity_type_title, '')) = ''
               OR TRIM(COALESCE(a.activity_type_title, '')) = 'فعالیت'
               OR TRIM(COALESCE(a.activity_type_title, '')) = 'نامشخص'
            LIMIT 10000
        ");
        if (empty($rows)) {
            return;
        }

        $lookupTitles = [];
        foreach ($this->runSelectAll("
            SELECT COALESCE(li.value, '') as lookup_value, COALESCE(li.title, '') as lookup_title
            FROM lookup_items li
            INNER JOIN lookup_groups lg ON lg.id = li.group_id
            WHERE lg.code = 'activity_types_fallback'
        ") as $lookupRow) {
            $lookupValue = trim((string)($lookupRow['lookup_value'] ?? ''));
            if ($lookupValue !== '' && !isset($lookupTitles[$lookupValue])) {
                $lookupTitles[$lookupValue] = trim((string)($lookupRow['lookup_title'] ?? ''));
            }
        }

        $stmt = $pdo->prepare("UPDATE activities SET activity_type_title = ?, last_sync = NOW() WHERE id = ?");
        foreach ($rows as $row) {
            $rawTitle = $this->decodeActivityTypeTitleFromRawJson((string)($row['raw_json'] ?? ''));
            $lookupTitle = $lookupTitles[trim((string)($row['activity_type_id'] ?? ''))] ?? '';
            $title = trim((string)($row['activity_type_title'] ?? ''));
            if ($title === '' || $title === 'فعالیت' || $title === 'نامشخص') {
                $title = $rawTitle !== '' ? $rawTitle : $lookupTitle;
            }
            if ($title === '' || $title === 'فعالیت' || $title === 'نامشخص') {
                $title = $this->normalizeActivityTypeLabelPhp((string)($row['stage'] ?? ''));
            }
            $title = trim($title);
            if ($title === '' || $title === 'فعالیت') {
                $title = 'نامشخص';
            }
            $stmt->execute([$title, (int)$row['id']]);
        }
    }

    public function getDailyActivitiesByType(array $filters): array
    {
        $pdo = $this->db->getPdo();
        $parts = [];
        $params = [];

        if (!empty($filters['date_from'])) {
            $parts[] = "DATE(COALESCE(a.register_date, a.created_at)) >= ?";
            $params[] = $filters['date_from'];
        }
        if (!empty($filters['date_to'])) {
            $parts[] = "DATE(COALESCE(a.register_date, a.created_at)) <= ?";
            $params[] = $filters['date_to'];
        }
        if (!empty($filters['owner_didar_ids_csv'])) {
            $parts[] = "FIND_IN_SET(a.owner_didar_id, ?)";
            $params[] = $filters['owner_didar_ids_csv'];
        } elseif (!empty($filters['owner_didar_id']) && trim($filters['owner_didar_id']) !== '') {
            $parts[] = "a.owner_didar_id = ?";
            $params[] = trim($filters['owner_didar_id']);
        }

        $where = !empty($parts) ? implode(' AND ', $parts) : '1=1';
        $activityTypeExpr = $this->buildCanonicalActivityTypeExpr('a');

        $sql = "
            SELECT
                DATE(COALESCE(a.register_date, a.created_at)) as date,
                {$activityTypeExpr} as activity_type,
                COUNT(*) as activities_count
            FROM activities a
            WHERE {$where}
            GROUP BY DATE(COALESCE(a.register_date, a.created_at)), {$activityTypeExpr}
            ORDER BY date ASC, activity_type ASC
        ";

        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    /**
     * KPI های عملیاتی روزانه (کلی + تفکیک کارشناس)
     */
    public function getDailyOperationalKpis(array $filters): array
    {
        $pdo = $this->db->getPdo();
        $paymentsByDealSql = $this->buildUnifiedPaymentsByDealSql();
        [$dateFrom, $dateTo] = $this->dateRangeFromFilters($filters);
        $ownerFilterCsv = !empty($filters['owner_didar_ids_csv'])
            ? trim((string)$filters['owner_didar_ids_csv'])
            : (!empty($filters['owner_didar_id']) && trim((string)$filters['owner_didar_id']) !== '' ? trim((string)$filters['owner_didar_id']) : null);
        $productFilterCsv = !empty($filters['product_ids_csv'])
            ? trim((string)$filters['product_ids_csv'])
            : (!empty($filters['product_id']) ? (string)$filters['product_id'] : null);

        $overall = [];
        $byExpert = [];

        $initOverall = function (string $date) use (&$overall) {
            if (!isset($overall[$date])) {
                $overall[$date] = [
                    'date' => $date,
                    'new_leads_count' => 0,
                    'new_deals_for_existing_leads_count' => 0,
                    'activities_count' => 0,
                    'new_leads_with_activity_count' => 0,
                    'leads_created_with_payment_count' => 0,
                    'deals_created_with_payment_count' => 0,
                    'leads_without_activity_count' => 0,
                    'deals_without_activity_count' => 0
                ];
            }
        };

        $initExpert = function (string $date, string $ownerDidarId, string $ownerName) use (&$byExpert) {
            $key = $date . '|' . $ownerDidarId;
            if (!isset($byExpert[$key])) {
                $byExpert[$key] = [
                    'date' => $date,
                    'owner_didar_id' => $ownerDidarId,
                    'owner_name' => $ownerName !== '' ? $ownerName : 'بدون مسئول',
                    'new_leads_count' => 0,
                    'new_deals_for_existing_leads_count' => 0,
                    'activities_count' => 0,
                    'new_leads_with_activity_count' => 0,
                    'leads_created_with_payment_count' => 0,
                    'deals_created_with_payment_count' => 0,
                    'leads_without_activity_count' => 0,
                    'deals_without_activity_count' => 0
                ];
            }
            return $key;
        };

        // 1) لید جدید
        $params = [$dateFrom, $dateTo];
        $whereOwner = '';
        if ($ownerFilterCsv !== null) {
            $whereOwner = ' AND FIND_IN_SET(p.owner_didar_id, ?)';
            $params[] = $ownerFilterCsv;
        }
        $sql = "
            SELECT
                DATE(p.register_time) as report_date,
                COALESCE(p.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, '') as owner_name,
                COUNT(*) as total_count
            FROM persons p
            LEFT JOIN users u ON u.didar_user_id = p.owner_didar_id
            WHERE DATE(p.register_time) BETWEEN ? AND ?
              {$whereOwner}
            GROUP BY DATE(p.register_time), COALESCE(p.owner_didar_id, ''), COALESCE(u.display_name, '')
        ";
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
            $date = $row['report_date'];
            $count = (int)($row['total_count'] ?? 0);
            $initOverall($date);
            $overall[$date]['new_leads_count'] += $count;
            $expertKey = $initExpert($date, (string)$row['owner_didar_id'], (string)$row['owner_name']);
            $byExpert[$expertKey]['new_leads_count'] += $count;
        }

        // 2) معامله جدید برای لیدهای قبلی
        $params = [$dateFrom, $dateTo];
        $whereOwner = '';
        if ($ownerFilterCsv !== null) {
            $whereOwner = ' AND FIND_IN_SET(d.owner_didar_id, ?)';
            $params[] = $ownerFilterCsv;
        }
        $whereProduct = '';
        if ($productFilterCsv !== null && $productFilterCsv !== '') {
            $whereProduct = ' AND EXISTS (SELECT 1 FROM deal_products dp WHERE dp.deal_id = d.id AND FIND_IN_SET(dp.product_id, ?))';
            $params[] = $productFilterCsv;
        }
        $sql = "
            SELECT
                DATE(COALESCE(d.register_time, d.created_at)) as report_date,
                COALESCE(d.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, '') as owner_name,
                COUNT(*) as total_count
            FROM deals d
            INNER JOIN persons p ON p.didar_contact_id = d.contact_didar_id
            LEFT JOIN users u ON u.didar_user_id = d.owner_didar_id
            WHERE COALESCE(d.is_legacy_didar, 0) = 0
              AND DATE(COALESCE(d.register_time, d.created_at)) BETWEEN ? AND ?
              AND DATE(COALESCE(p.register_time, p.created_at)) < DATE(COALESCE(d.register_time, d.created_at))
              {$whereOwner}
              {$whereProduct}
            GROUP BY DATE(COALESCE(d.register_time, d.created_at)), COALESCE(d.owner_didar_id, ''), COALESCE(u.display_name, '')
        ";
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
            $date = $row['report_date'];
            $count = (int)($row['total_count'] ?? 0);
            $initOverall($date);
            $overall[$date]['new_deals_for_existing_leads_count'] += $count;
            $expertKey = $initExpert($date, (string)$row['owner_didar_id'], (string)$row['owner_name']);
            $byExpert[$expertKey]['new_deals_for_existing_leads_count'] += $count;
        }

        // 3) تعداد فعالیت روزانه
        $params = [$dateFrom, $dateTo];
        $whereOwner = '';
        if ($ownerFilterCsv !== null) {
            $whereOwner = ' AND FIND_IN_SET(a.owner_didar_id, ?)';
            $params[] = $ownerFilterCsv;
        }
        $sql = "
            SELECT
                DATE(COALESCE(a.register_date, a.created_at)) as report_date,
                COALESCE(a.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, '') as owner_name,
                COUNT(*) as total_count
            FROM activities a
            LEFT JOIN users u ON u.didar_user_id = a.owner_didar_id
            WHERE DATE(COALESCE(a.register_date, a.created_at)) BETWEEN ? AND ?
              {$whereOwner}
            GROUP BY DATE(COALESCE(a.register_date, a.created_at)), COALESCE(a.owner_didar_id, ''), COALESCE(u.display_name, '')
        ";
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
            $date = $row['report_date'];
            $count = (int)($row['total_count'] ?? 0);
            $initOverall($date);
            $overall[$date]['activities_count'] += $count;
            $expertKey = $initExpert($date, (string)$row['owner_didar_id'], (string)$row['owner_name']);
            $byExpert[$expertKey]['activities_count'] += $count;
        }

        // 4) چند لید جدید همان روز فعالیت داشته‌اند
        $params = [$dateFrom, $dateTo];
        $whereOwner = '';
        if ($ownerFilterCsv !== null) {
            $whereOwner = ' AND FIND_IN_SET(p.owner_didar_id, ?)';
            $params[] = $ownerFilterCsv;
        }
        $sql = "
            SELECT
                DATE(p.register_time) as report_date,
                COALESCE(p.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, '') as owner_name,
                COUNT(DISTINCT p.didar_contact_id) as total_count
            FROM persons p
            LEFT JOIN users u ON u.didar_user_id = p.owner_didar_id
            INNER JOIN activities a
                ON a.contact_didar_id = p.didar_contact_id
               AND DATE(COALESCE(a.register_date, a.created_at)) = DATE(p.register_time)
            WHERE DATE(p.register_time) BETWEEN ? AND ?
              {$whereOwner}
            GROUP BY DATE(p.register_time), COALESCE(p.owner_didar_id, ''), COALESCE(u.display_name, '')
        ";
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
            $date = $row['report_date'];
            $count = (int)($row['total_count'] ?? 0);
            $initOverall($date);
            $overall[$date]['new_leads_with_activity_count'] += $count;
            $expertKey = $initExpert($date, (string)$row['owner_didar_id'], (string)$row['owner_name']);
            $byExpert[$expertKey]['new_leads_with_activity_count'] += $count;
        }

        // 5) لیدهای ثبت‌شده روز که هر زمان پرداخت داشته‌اند
        $params = [$dateFrom, $dateTo];
        $whereOwner = '';
        if ($ownerFilterCsv !== null) {
            $whereOwner = ' AND FIND_IN_SET(p.owner_didar_id, ?)';
            $params[] = $ownerFilterCsv;
        }
        $whereProductSub = '';
        if ($productFilterCsv !== null && $productFilterCsv !== '') {
            $whereProductSub = ' AND EXISTS (SELECT 1 FROM deal_products dp WHERE dp.deal_id = d2.id AND FIND_IN_SET(dp.product_id, ?))';
            $params[] = $productFilterCsv;
        }
        $sql = "
            SELECT
                DATE(p.register_time) as report_date,
                COALESCE(p.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, '') as owner_name,
                COUNT(DISTINCT CASE WHEN paid.contact_didar_id IS NOT NULL THEN p.didar_contact_id END) as total_count
            FROM persons p
            LEFT JOIN users u ON u.didar_user_id = p.owner_didar_id
            LEFT JOIN (
                SELECT DISTINCT d2.contact_didar_id
                FROM deals d2
                INNER JOIN ({$paymentsByDealSql}) p2 ON p2.deal_id = d2.id
                WHERE COALESCE(d2.is_legacy_didar, 0) = 0
                  {$whereProductSub}
            ) paid ON paid.contact_didar_id = p.didar_contact_id
            WHERE DATE(p.register_time) BETWEEN ? AND ?
              {$whereOwner}
            GROUP BY DATE(p.register_time), COALESCE(p.owner_didar_id, ''), COALESCE(u.display_name, '')
        ";
        // برای ساب‌کوئری product، پارامتر قبل از بازه می‌آید
        if ($productFilterCsv !== null && $productFilterCsv !== '') {
            $stmt = $pdo->prepare($sql);
            $execParams = [$productFilterCsv, $dateFrom, $dateTo];
            if ($ownerFilterCsv !== null) {
                $execParams[] = $ownerFilterCsv;
            }
            $stmt->execute($execParams);
        } else {
            $stmt = $pdo->prepare($sql);
            $stmt->execute($params);
        }
        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
            $date = $row['report_date'];
            $count = (int)($row['total_count'] ?? 0);
            $initOverall($date);
            $overall[$date]['leads_created_with_payment_count'] += $count;
            $expertKey = $initExpert($date, (string)$row['owner_didar_id'], (string)$row['owner_name']);
            $byExpert[$expertKey]['leads_created_with_payment_count'] += $count;
        }

        // 6) معاملات ثبت‌شده روز که هر زمان پرداخت داشته‌اند
        $params = [$dateFrom, $dateTo];
        $whereOwner = '';
        if ($ownerFilterCsv !== null) {
            $whereOwner = ' AND FIND_IN_SET(d.owner_didar_id, ?)';
            $params[] = $ownerFilterCsv;
        }
        $whereProduct = '';
        if ($productFilterCsv !== null && $productFilterCsv !== '') {
            $whereProduct = ' AND EXISTS (SELECT 1 FROM deal_products dp WHERE dp.deal_id = d.id AND FIND_IN_SET(dp.product_id, ?))';
            $params[] = $productFilterCsv;
        }
        $sql = "
            SELECT
                DATE(COALESCE(d.register_time, d.created_at)) as report_date,
                COALESCE(d.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, '') as owner_name,
                COUNT(DISTINCT CASE WHEN paid.deal_id IS NOT NULL THEN d.id END) as total_count
            FROM deals d
            LEFT JOIN users u ON u.didar_user_id = d.owner_didar_id
            LEFT JOIN (
                SELECT DISTINCT deal_id
                FROM ({$paymentsByDealSql}) p3
            ) paid ON paid.deal_id = d.id
            WHERE COALESCE(d.is_legacy_didar, 0) = 0
              AND DATE(COALESCE(d.register_time, d.created_at)) BETWEEN ? AND ?
              {$whereOwner}
              {$whereProduct}
            GROUP BY DATE(COALESCE(d.register_time, d.created_at)), COALESCE(d.owner_didar_id, ''), COALESCE(u.display_name, '')
        ";
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
            $date = $row['report_date'];
            $count = (int)($row['total_count'] ?? 0);
            $initOverall($date);
            $overall[$date]['deals_created_with_payment_count'] += $count;
            $expertKey = $initExpert($date, (string)$row['owner_didar_id'], (string)$row['owner_name']);
            $byExpert[$expertKey]['deals_created_with_payment_count'] += $count;
        }

        // 7) لیدهای ثبت‌شده روز که هیچ فعالیتی ندارند
        $params = [$dateFrom, $dateTo];
        $whereOwner = '';
        if ($ownerFilterCsv !== null) {
            $whereOwner = ' AND FIND_IN_SET(p.owner_didar_id, ?)';
            $params[] = $ownerFilterCsv;
        }
        $sql = "
            SELECT
                DATE(p.register_time) as report_date,
                COALESCE(p.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, '') as owner_name,
                COUNT(DISTINCT p.didar_contact_id) as total_count
            FROM persons p
            LEFT JOIN users u ON u.didar_user_id = p.owner_didar_id
            LEFT JOIN activities a ON a.contact_didar_id = p.didar_contact_id
            WHERE DATE(p.register_time) BETWEEN ? AND ?
              {$whereOwner}
              AND a.id IS NULL
            GROUP BY DATE(p.register_time), COALESCE(p.owner_didar_id, ''), COALESCE(u.display_name, '')
        ";
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
            $date = $row['report_date'];
            $count = (int)($row['total_count'] ?? 0);
            $initOverall($date);
            $overall[$date]['leads_without_activity_count'] += $count;
            $expertKey = $initExpert($date, (string)$row['owner_didar_id'], (string)$row['owner_name']);
            $byExpert[$expertKey]['leads_without_activity_count'] += $count;
        }

        // 8) معاملات ثبت‌شده روز که هیچ فعالیتی ندارند
        $params = [$dateFrom, $dateTo];
        $whereOwner = '';
        if ($ownerFilterCsv !== null) {
            $whereOwner = ' AND FIND_IN_SET(d.owner_didar_id, ?)';
            $params[] = $ownerFilterCsv;
        }
        $whereProduct = '';
        if ($productFilterCsv !== null && $productFilterCsv !== '') {
            $whereProduct = ' AND EXISTS (SELECT 1 FROM deal_products dp WHERE dp.deal_id = d.id AND FIND_IN_SET(dp.product_id, ?))';
            $params[] = $productFilterCsv;
        }
        $sql = "
            SELECT
                DATE(COALESCE(d.register_time, d.created_at)) as report_date,
                COALESCE(d.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, '') as owner_name,
                COUNT(DISTINCT d.id) as total_count
            FROM deals d
            LEFT JOIN users u ON u.didar_user_id = d.owner_didar_id
            LEFT JOIN activities a ON a.deal_didar_id = d.didar_deal_id
            WHERE COALESCE(d.is_legacy_didar, 0) = 0
              AND DATE(COALESCE(d.register_time, d.created_at)) BETWEEN ? AND ?
              {$whereOwner}
              {$whereProduct}
              AND a.id IS NULL
            GROUP BY DATE(COALESCE(d.register_time, d.created_at)), COALESCE(d.owner_didar_id, ''), COALESCE(u.display_name, '')
        ";
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
            $date = $row['report_date'];
            $count = (int)($row['total_count'] ?? 0);
            $initOverall($date);
            $overall[$date]['deals_without_activity_count'] += $count;
            $expertKey = $initExpert($date, (string)$row['owner_didar_id'], (string)$row['owner_name']);
            $byExpert[$expertKey]['deals_without_activity_count'] += $count;
        }

        // 9) تفکیک نوع فعالیت روزانه به ازای کارشناس
        $params = [$dateFrom, $dateTo];
        $whereOwner = '';
        if ($ownerFilterCsv !== null) {
            $whereOwner = ' AND FIND_IN_SET(a.owner_didar_id, ?)';
            $params[] = $ownerFilterCsv;
        }
        $activityTypeExpr = $this->buildCanonicalActivityTypeExpr('a');
        $sql = "
            SELECT
                DATE(COALESCE(a.register_date, a.created_at)) as report_date,
                COALESCE(a.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, '') as owner_name,
                {$activityTypeExpr} as activity_type,
                COUNT(*) as total_count
            FROM activities a
            LEFT JOIN users u ON u.didar_user_id = a.owner_didar_id
            WHERE DATE(COALESCE(a.register_date, a.created_at)) BETWEEN ? AND ?
              {$whereOwner}
            GROUP BY DATE(COALESCE(a.register_date, a.created_at)), COALESCE(a.owner_didar_id, ''), COALESCE(u.display_name, ''), {$activityTypeExpr}
            ORDER BY report_date ASC, owner_name ASC, activity_type ASC
        ";
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        $activityTypeByExpertDaily = $stmt->fetchAll(PDO::FETCH_ASSOC);

        // 10) لیدهای جدید همان روز + چه فعالیتی + توسط کدام کارشناس
        $params = [$dateFrom, $dateTo];
        $whereOwner = '';
        if ($ownerFilterCsv !== null) {
            $whereOwner = ' AND FIND_IN_SET(p.owner_didar_id, ?)';
            $params[] = $ownerFilterCsv;
        }
        $activityTypeExpr = $this->buildCanonicalActivityTypeExpr('a');
        $sql = "
            SELECT
                DATE(p.register_time) as report_date,
                COALESCE(p.owner_didar_id, '') as lead_owner_didar_id,
                COALESCE(lu.display_name, '') as lead_owner_name,
                COALESCE(a.owner_didar_id, '') as activity_owner_didar_id,
                COALESCE(au.display_name, '') as activity_owner_name,
                {$activityTypeExpr} as activity_type,
                COUNT(*) as activity_count,
                COUNT(DISTINCT p.didar_contact_id) as leads_count
            FROM persons p
            INNER JOIN activities a
                ON a.contact_didar_id = p.didar_contact_id
               AND DATE(COALESCE(a.register_date, a.created_at)) = DATE(p.register_time)
            LEFT JOIN users lu ON lu.didar_user_id = p.owner_didar_id
            LEFT JOIN users au ON au.didar_user_id = a.owner_didar_id
            WHERE DATE(p.register_time) BETWEEN ? AND ?
              {$whereOwner}
            GROUP BY
                DATE(p.register_time),
                COALESCE(p.owner_didar_id, ''),
                COALESCE(lu.display_name, ''),
                COALESCE(a.owner_didar_id, ''),
                COALESCE(au.display_name, ''),
                {$activityTypeExpr}
            ORDER BY report_date ASC, lead_owner_name ASC, activity_owner_name ASC, activity_type ASC
        ";
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        $newLeadsActivityByExpertDaily = $stmt->fetchAll(PDO::FETCH_ASSOC);

        ksort($overall);
        usort($byExpert, function ($a, $b) {
            if ($a['date'] === $b['date']) {
                return strcmp((string)$a['owner_name'], (string)$b['owner_name']);
            }
            return strcmp((string)$a['date'], (string)$b['date']);
        });

        return [
            'overall_daily' => array_values($overall),
            'by_expert_daily' => array_values($byExpert),
            'activity_type_by_expert_daily' => $activityTypeByExpertDaily,
            'new_leads_activity_by_expert_daily' => $newLeadsActivityByExpertDaily
        ];
    }

    /**
     * جدول‌های پیشرفته KPI (روزانه + تفکیک کارشناس)
     */
    public function getAdvancedKpiTables(array $filters): array
    {
        $skipActivityTables = !empty($filters['_perf_skip_activity_tables']);
        $whereDeals = $this->buildWhereClauseForDeals($filters, 'd');
        $paramsDeals = $this->buildParamsForDeals($filters);
        $statusCsv = $this->normalizeCsvList($filters['deal_statuses_csv'] ?? '');
        $leadTypeCsv = $this->normalizeCsvList($filters['deal_lead_types_csv'] ?? '');
        $paymentsByDealSql = $this->buildUnifiedPaymentsByDealSql();
        $legacyOpenClause = $this->buildLegacyDealFilterClause($filters, 'd2');
        $legacyOpenClause = $legacyOpenClause !== '' ? (' AND ' . $legacyOpenClause) : '';
        $legacyDealsClauseForD = $this->buildLegacyDealFilterClause($filters, 'd');
        $legacyDealsClauseForD = $legacyDealsClauseForD !== '' ? (' AND ' . $legacyDealsClauseForD) : '';
        $legacyDealsClauseForD4 = $this->buildLegacyDealFilterClause($filters, 'd4');
        $legacyDealsClauseForD4 = $legacyDealsClauseForD4 !== '' ? (' AND ' . $legacyDealsClauseForD4) : '';

        $dealsBaseSql = "
            SELECT
                d.id as deal_id,
                DATE(COALESCE(d.register_time, d.created_at)) as deal_date,
                COALESCE(d.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name,
                COALESCE(d.contact_didar_id, '') as contact_didar_id,
                COALESCE(p.sale_status, '') as lead_sale_status,
                DATE(COALESCE(p.register_time, p.created_at)) as lead_register_time,
                COALESCE(p.register_time_jalali, '') as lead_register_time_jalali,
                {$this->buildDealProductHintExpr('d')} as deal_product_hint,
                CASE
                    WHEN DATE(COALESCE(p.register_time, p.created_at)) = DATE(COALESCE(d.register_time, d.created_at)) THEN 'new'
                    ELSE 'old'
                END as lead_type,
                CASE
                    WHEN d.status = 'Won' THEN 'won'
                    WHEN d.status = 'Lost' THEN 'lost'
                    ELSE 'pending'
                END as deal_status_group,
                COALESCE(d.payable_amount, 0) as payable_amount,
                COALESCE(pay.total_paid, 0) as total_paid,
                pay.first_payment_date,
                DATE(d.lost_time) as lost_date,
                CASE WHEN COALESCE(pay.total_paid, 0) > 0 THEN 1 ELSE 0 END as has_payment,
                CASE WHEN COALESCE(od.open_count, 0) > 0 THEN 1 ELSE 0 END as has_open_deal,
                COALESCE(
                    NULLIF(TRIM(d.failure_reason_text), ''),
                    NULLIF(TRIM(d.failure_reason_description), ''),
                    CASE TRIM(d.failure_reason_code)
                        WHEN 'no_financial_ability' THEN 'عدم توان مالی'
                        WHEN 'no_purchase_intent' THEN 'فعلا قصد خرید ندارد'
                        WHEN 'competitor_purchase' THEN 'خرید از رقبا'
                        WHEN 'wrong_number' THEN 'شماره اشتباه'
                        WHEN 'irrelevant_lead' THEN 'لید نامرتبط'
                        WHEN 'other' THEN 'سایر'
                        ELSE NULL
                    END,
                    NULLIF(TRIM(d.failure_reason_code), ''),
                    'نامشخص'
                ) as failure_reason
            FROM deals d
            LEFT JOIN users u ON u.didar_user_id = d.owner_didar_id
            LEFT JOIN persons p ON p.didar_contact_id = d.contact_didar_id
            LEFT JOIN ({$paymentsByDealSql}) pay ON pay.deal_id = d.id
            LEFT JOIN (
                SELECT
                    d2.contact_didar_id,
                    COUNT(*) as open_count
                FROM deals d2
                WHERE d2.status NOT IN ('Won', 'Lost')
                  {$legacyOpenClause}
                GROUP BY d2.contact_didar_id
            ) od ON od.contact_didar_id = d.contact_didar_id
            WHERE {$whereDeals}
        ";

        $tableDealStatus = $this->runSelectAll("
            SELECT
                b.deal_date as date,
                b.owner_didar_id,
                b.owner_name,
                COUNT(*) as deal_total,
                SUM(CASE WHEN b.lead_type = 'new' THEN 1 ELSE 0 END) as deal_new_total,
                SUM(CASE WHEN b.lead_type = 'old' THEN 1 ELSE 0 END) as deal_old_total,
                SUM(CASE WHEN b.deal_status_group = 'pending' THEN 1 ELSE 0 END) as deal_pending_total,
                SUM(CASE WHEN b.deal_status_group = 'pending' AND b.lead_type = 'new' THEN 1 ELSE 0 END) as deal_pending_new,
                SUM(CASE WHEN b.deal_status_group = 'pending' AND b.lead_type = 'old' THEN 1 ELSE 0 END) as deal_pending_old,
                SUM(CASE WHEN b.has_payment = 1 THEN 1 ELSE 0 END) as deal_with_sales_total,
                SUM(CASE WHEN b.has_payment = 1 AND b.lead_type = 'new' THEN 1 ELSE 0 END) as deal_with_sales_new,
                SUM(CASE WHEN b.has_payment = 1 AND b.lead_type = 'old' THEN 1 ELSE 0 END) as deal_with_sales_old,
                SUM(CASE WHEN b.has_payment = 0 AND b.deal_status_group = 'pending' THEN 1 ELSE 0 END) as deal_without_sales_total,
                SUM(CASE WHEN b.has_payment = 0 AND b.deal_status_group = 'pending' AND b.lead_type = 'new' THEN 1 ELSE 0 END) as deal_without_sales_new,
                SUM(CASE WHEN b.has_payment = 0 AND b.deal_status_group = 'pending' AND b.lead_type = 'old' THEN 1 ELSE 0 END) as deal_without_sales_old,
                SUM(CASE WHEN b.deal_status_group = 'won' THEN 1 ELSE 0 END) as deal_won_total,
                SUM(CASE WHEN b.deal_status_group = 'won' AND b.lead_type = 'new' THEN 1 ELSE 0 END) as deal_won_new,
                SUM(CASE WHEN b.deal_status_group = 'won' AND b.lead_type = 'old' THEN 1 ELSE 0 END) as deal_won_old,
                SUM(CASE WHEN b.deal_status_group = 'lost' THEN 1 ELSE 0 END) as deal_lost_total,
                SUM(CASE WHEN b.deal_status_group = 'lost' AND b.lead_type = 'new' THEN 1 ELSE 0 END) as deal_lost_new,
                SUM(CASE WHEN b.deal_status_group = 'lost' AND b.lead_type = 'old' THEN 1 ELSE 0 END) as deal_lost_old
            FROM ({$dealsBaseSql}) b
            GROUP BY b.deal_date, b.owner_didar_id, b.owner_name
            ORDER BY b.deal_date ASC, b.owner_name ASC
        ", $paramsDeals);

        [$dateFrom, $dateTo] = $this->dateRangeFromFilters($filters);
        $leadParams = [$dateFrom, $dateTo];
        $leadOwnerWhere = '';
        if (!empty($filters['owner_didar_ids_csv'])) {
            $leadOwnerWhere = ' AND FIND_IN_SET(p.owner_didar_id, ?)';
            $leadParams[] = $filters['owner_didar_ids_csv'];
        } elseif (!empty($filters['owner_didar_id']) && trim((string)$filters['owner_didar_id']) !== '') {
            $leadOwnerWhere = ' AND p.owner_didar_id = ?';
            $leadParams[] = trim((string)$filters['owner_didar_id']);
        }

        $leadDealExistsClause = '';
        $leadDealFilterParts = [];
        $leadDealFilterParams = [];
        $applyLeadDealFilters =
            !empty($filters['product_ids_csv']) ||
            !empty($filters['product_id']) ||
            ($statusCsv !== '' && !$this->csvHasAll($statusCsv, ['pending', 'won', 'lost'])) ||
            ($leadTypeCsv !== '' && !$this->csvHasAll($leadTypeCsv, ['new', 'old']));

        if ($applyLeadDealFilters) {
            if ($this->db->hasColumn('deals', 'is_legacy_didar')) {
                $leadDealFilterParts[] = "COALESCE(d3.is_legacy_didar, 0) = 0";
            }
            $leadDealFilterParts[] = "DATE(COALESCE(d3.register_time, d3.created_at)) BETWEEN ? AND ?";
            $leadDealFilterParams[] = $dateFrom;
            $leadDealFilterParams[] = $dateTo;

            if (!empty($filters['product_ids_csv'])) {
                $leadDealFilterParts[] = "EXISTS (SELECT 1 FROM deal_products dp3 WHERE dp3.deal_id = d3.id AND FIND_IN_SET(dp3.product_id, ?))";
                $leadDealFilterParams[] = $filters['product_ids_csv'];
            } elseif (!empty($filters['product_id'])) {
                $leadDealFilterParts[] = "EXISTS (SELECT 1 FROM deal_products dp3 WHERE dp3.deal_id = d3.id AND dp3.product_id = ?)";
                $leadDealFilterParams[] = $filters['product_id'];
            }

            if ($statusCsv !== '' && !$this->csvHasAll($statusCsv, ['pending', 'won', 'lost'])) {
                $leadDealFilterParts[] = "(
                    (FIND_IN_SET('pending', ?) > 0 AND d3.status NOT IN ('Won', 'Lost'))
                    OR (FIND_IN_SET('won', ?) > 0 AND d3.status = 'Won')
                    OR (FIND_IN_SET('lost', ?) > 0 AND d3.status = 'Lost')
                )";
                $leadDealFilterParams[] = $statusCsv;
                $leadDealFilterParams[] = $statusCsv;
                $leadDealFilterParams[] = $statusCsv;
            }

            if ($leadTypeCsv !== '' && !$this->csvHasAll($leadTypeCsv, ['new', 'old'])) {
                $leadDealFilterParts[] = "(
                    (FIND_IN_SET('new', ?) > 0 AND DATE(COALESCE(p.register_time, p.created_at)) = DATE(COALESCE(d3.register_time, d3.created_at)))
                    OR (FIND_IN_SET('old', ?) > 0 AND DATE(COALESCE(p.register_time, p.created_at)) < DATE(COALESCE(d3.register_time, d3.created_at)))
                )";
                $leadDealFilterParams[] = $leadTypeCsv;
                $leadDealFilterParams[] = $leadTypeCsv;
            }

            $leadDealExistsClause = "
                AND EXISTS (
                    SELECT 1
                    FROM deals d3
                    WHERE d3.contact_didar_id = p.didar_contact_id
                      AND " . implode(' AND ', $leadDealFilterParts) . "
                )
            ";
            $leadParams = array_merge($leadParams, $leadDealFilterParams);
        }

        $leadsBaseSql = "
            SELECT
                DATE(COALESCE(p.register_time, p.created_at)) as lead_date,
                COALESCE(p.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name,
                COALESCE(p.sale_status, 0) as sale_status,
                CASE WHEN COALESCE(od.open_count, 0) > 0 THEN 1 ELSE 0 END as has_open_deal,
                cp.first_payment_date,
                CASE WHEN cp.first_payment_date IS NOT NULL THEN 1 ELSE 0 END as has_sale_payment
            FROM persons p
            LEFT JOIN users u ON u.didar_user_id = p.owner_didar_id
            LEFT JOIN (
                SELECT
                    d2.contact_didar_id,
                    COUNT(*) as open_count
                FROM deals d2
                WHERE d2.status NOT IN ('Won', 'Lost')
                  {$legacyOpenClause}
                GROUP BY d2.contact_didar_id
            ) od ON od.contact_didar_id = p.didar_contact_id
            LEFT JOIN (
                SELECT
                    d4.contact_didar_id,
                    MIN(DATE(pay4.first_payment_date)) as first_payment_date
                FROM deals d4
                LEFT JOIN ({$paymentsByDealSql}) pay4 ON pay4.deal_id = d4.id
                WHERE pay4.first_payment_date IS NOT NULL
                  {$legacyDealsClauseForD4}
                GROUP BY d4.contact_didar_id
            ) cp ON cp.contact_didar_id = p.didar_contact_id
            WHERE DATE(COALESCE(p.register_time, p.created_at)) BETWEEN ? AND ?
              {$leadOwnerWhere}
              {$leadDealExistsClause}
        ";

        $tableLeadStatus = $this->runSelectAll("
            SELECT
                l.lead_date as date,
                l.owner_didar_id,
                l.owner_name,
                COUNT(*) as total_leads,
                SUM(CASE WHEN l.has_sale_payment = 1 THEN 1 ELSE 0 END) as leads_with_sales,
                SUM(CASE WHEN l.has_sale_payment = 1 AND l.first_payment_date = l.lead_date THEN 1 ELSE 0 END) as leads_with_new_sales,
                SUM(CASE WHEN l.has_sale_payment = 1 AND l.first_payment_date < l.lead_date THEN 1 ELSE 0 END) as leads_with_previous_sales,
                SUM(CASE WHEN l.has_sale_payment = 0 THEN 1 ELSE 0 END) as leads_without_sales,
                SUM(CASE WHEN l.has_sale_payment = 0 AND l.has_open_deal = 1 THEN 1 ELSE 0 END) as leads_without_sales_with_current_deal,
                SUM(CASE WHEN l.has_sale_payment = 0 AND l.has_open_deal = 0 THEN 1 ELSE 0 END) as leads_without_sales_without_current_deal,
                COUNT(*) as new_leads_count
            FROM ({$leadsBaseSql}) l
            GROUP BY l.lead_date, l.owner_didar_id, l.owner_name
            ORDER BY l.lead_date ASC, l.owner_name ASC
        ", $leadParams);

        $tablePaymentMix = $this->runSelectAll("
            SELECT
                b.deal_date as date,
                b.owner_didar_id,
                b.owner_name,
                COUNT(DISTINCT CASE WHEN b.lead_type = 'new' AND b.has_payment = 1 THEN b.contact_didar_id END) as new_leads_with_payment,
                COUNT(DISTINCT CASE WHEN b.lead_type = 'new' AND b.has_payment = 0 AND b.deal_status_group <> 'lost' THEN b.contact_didar_id END) as new_leads_without_payment,
                COUNT(DISTINCT CASE WHEN b.lead_type = 'new' AND b.deal_status_group = 'lost' THEN b.contact_didar_id END) as new_leads_failed,
                COUNT(DISTINCT CASE WHEN b.lead_type = 'old' AND b.has_payment = 1 THEN b.contact_didar_id END) as old_leads_with_payment,
                COUNT(DISTINCT CASE WHEN b.lead_type = 'old' AND b.has_payment = 0 AND b.deal_status_group <> 'lost' THEN b.contact_didar_id END) as old_leads_without_payment,
                COUNT(DISTINCT CASE WHEN b.lead_type = 'old' AND b.deal_status_group = 'lost' THEN b.contact_didar_id END) as old_leads_failed
            FROM ({$dealsBaseSql}) b
            GROUP BY b.deal_date, b.owner_didar_id, b.owner_name
            ORDER BY b.deal_date ASC, b.owner_name ASC
        ", $paramsDeals);

        $tablePaymentStatus = $this->runSelectAll("
            SELECT
                b.deal_date as date,
                b.owner_didar_id,
                b.owner_name,
                SUM(CASE WHEN b.has_payment = 1 AND b.payable_amount > 0 AND b.total_paid < b.payable_amount THEN 1 ELSE 0 END) as outstanding_deals_count,
                SUM(CASE WHEN b.has_payment = 1 AND b.payable_amount > 0 AND b.total_paid >= b.payable_amount THEN 1 ELSE 0 END) as settled_deals_count,
                SUM(CASE WHEN b.has_payment = 1 AND b.payable_amount > 0 AND b.total_paid < b.payable_amount THEN (b.payable_amount - b.total_paid) ELSE 0 END) as outstanding_amount_sum,
                SUM(CASE WHEN b.has_payment = 1 AND b.payable_amount > 0 AND b.total_paid >= b.payable_amount THEN b.payable_amount ELSE 0 END) as settled_contract_amount_sum,
                SUM(CASE WHEN b.has_payment = 1 THEN b.total_paid ELSE 0 END) as payments_sum
            FROM ({$dealsBaseSql}) b
            GROUP BY b.deal_date, b.owner_didar_id, b.owner_name
            ORDER BY b.deal_date ASC, b.owner_name ASC
        ", $paramsDeals);

        $tableConversionStatus = $this->runSelectAll("
            SELECT
                b.deal_date as date,
                b.owner_didar_id,
                b.owner_name,
                ROUND((SUM(CASE WHEN b.has_payment = 1 THEN 1 ELSE 0 END) / NULLIF(COUNT(*), 0)) * 100, 2) as conversion_rate_total,
                ROUND((SUM(CASE WHEN b.has_payment = 0 THEN 1 ELSE 0 END) / NULLIF(COUNT(*), 0)) * 100, 2) as non_conversion_rate_total,
                ROUND((SUM(CASE WHEN b.lead_type = 'new' AND b.has_payment = 1 THEN 1 ELSE 0 END) / NULLIF(SUM(CASE WHEN b.lead_type = 'new' THEN 1 ELSE 0 END), 0)) * 100, 2) as conversion_rate_new,
                ROUND((SUM(CASE WHEN b.lead_type = 'new' AND b.has_payment = 0 THEN 1 ELSE 0 END) / NULLIF(SUM(CASE WHEN b.lead_type = 'new' THEN 1 ELSE 0 END), 0)) * 100, 2) as non_conversion_rate_new,
                ROUND((SUM(CASE WHEN b.lead_type = 'old' AND b.has_payment = 1 THEN 1 ELSE 0 END) / NULLIF(SUM(CASE WHEN b.lead_type = 'old' THEN 1 ELSE 0 END), 0)) * 100, 2) as conversion_rate_old,
                ROUND((SUM(CASE WHEN b.lead_type = 'old' AND b.has_payment = 0 THEN 1 ELSE 0 END) / NULLIF(SUM(CASE WHEN b.lead_type = 'old' THEN 1 ELSE 0 END), 0)) * 100, 2) as non_conversion_rate_old,
                SUM(CASE WHEN b.has_payment = 1 THEN 1 ELSE 0 END) as sales_total_count,
                SUM(CASE WHEN b.lead_type = 'new' AND b.has_payment = 1 THEN 1 ELSE 0 END) as sales_new_count,
                SUM(CASE WHEN b.lead_type = 'old' AND b.has_payment = 1 THEN 1 ELSE 0 END) as sales_old_count,
                COUNT(*) as total_deals_count,
                SUM(CASE WHEN b.lead_type = 'new' THEN 1 ELSE 0 END) as total_new_deals_count,
                SUM(CASE WHEN b.lead_type = 'old' THEN 1 ELSE 0 END) as total_old_deals_count,
                SUM(CASE WHEN b.has_payment = 1 THEN b.total_paid ELSE 0 END) as total_collected_amount,
                SUM(CASE WHEN b.has_payment = 1 AND b.payable_amount > 0 AND b.total_paid < b.payable_amount THEN (b.payable_amount - b.total_paid) ELSE 0 END) as outstanding_amount_sum
            FROM ({$dealsBaseSql}) b
            GROUP BY b.deal_date, b.owner_didar_id, b.owner_name
            ORDER BY b.deal_date ASC, b.owner_name ASC
        ", $paramsDeals);

        $tableConversionTime = $this->runSelectAll("
            SELECT
                b.deal_date as date,
                b.owner_didar_id,
                b.owner_name,
                ROUND(AVG(CASE WHEN b.has_payment = 1 AND b.first_payment_date IS NOT NULL THEN DATEDIFF(b.first_payment_date, b.deal_date) END), 2) as avg_conversion_days_total,
                SUM(CASE WHEN b.has_payment = 1 AND b.first_payment_date IS NOT NULL THEN DATEDIFF(b.first_payment_date, b.deal_date) ELSE 0 END) as sum_conversion_days_total,
                SUM(CASE WHEN b.has_payment = 1 AND b.first_payment_date IS NOT NULL THEN 1 ELSE 0 END) as count_conversion_days_total,

                ROUND(AVG(CASE WHEN b.lead_type = 'new' AND b.has_payment = 1 AND b.first_payment_date IS NOT NULL THEN DATEDIFF(b.first_payment_date, b.deal_date) END), 2) as avg_conversion_days_new,
                SUM(CASE WHEN b.lead_type = 'new' AND b.has_payment = 1 AND b.first_payment_date IS NOT NULL THEN DATEDIFF(b.first_payment_date, b.deal_date) ELSE 0 END) as sum_conversion_days_new,
                SUM(CASE WHEN b.lead_type = 'new' AND b.has_payment = 1 AND b.first_payment_date IS NOT NULL THEN 1 ELSE 0 END) as count_conversion_days_new,

                ROUND(AVG(CASE WHEN b.lead_type = 'old' AND b.has_payment = 1 AND b.first_payment_date IS NOT NULL THEN DATEDIFF(b.first_payment_date, b.deal_date) END), 2) as avg_conversion_days_old,
                SUM(CASE WHEN b.lead_type = 'old' AND b.has_payment = 1 AND b.first_payment_date IS NOT NULL THEN DATEDIFF(b.first_payment_date, b.deal_date) ELSE 0 END) as sum_conversion_days_old,
                SUM(CASE WHEN b.lead_type = 'old' AND b.has_payment = 1 AND b.first_payment_date IS NOT NULL THEN 1 ELSE 0 END) as count_conversion_days_old,

                ROUND(AVG(CASE WHEN b.deal_status_group = 'lost' AND b.lost_date IS NOT NULL THEN DATEDIFF(b.lost_date, b.deal_date) END), 2) as avg_failure_days_total,
                SUM(CASE WHEN b.deal_status_group = 'lost' AND b.lost_date IS NOT NULL THEN DATEDIFF(b.lost_date, b.deal_date) ELSE 0 END) as sum_failure_days_total,
                SUM(CASE WHEN b.deal_status_group = 'lost' AND b.lost_date IS NOT NULL THEN 1 ELSE 0 END) as count_failure_days_total,

                ROUND(AVG(CASE WHEN b.lead_type = 'new' AND b.deal_status_group = 'lost' AND b.lost_date IS NOT NULL THEN DATEDIFF(b.lost_date, b.deal_date) END), 2) as avg_failure_days_new,
                SUM(CASE WHEN b.lead_type = 'new' AND b.deal_status_group = 'lost' AND b.lost_date IS NOT NULL THEN DATEDIFF(b.lost_date, b.deal_date) ELSE 0 END) as sum_failure_days_new,
                SUM(CASE WHEN b.lead_type = 'new' AND b.deal_status_group = 'lost' AND b.lost_date IS NOT NULL THEN 1 ELSE 0 END) as count_failure_days_new,

                ROUND(AVG(CASE WHEN b.lead_type = 'old' AND b.deal_status_group = 'lost' AND b.lost_date IS NOT NULL THEN DATEDIFF(b.lost_date, b.deal_date) END), 2) as avg_failure_days_old,
                SUM(CASE WHEN b.lead_type = 'old' AND b.deal_status_group = 'lost' AND b.lost_date IS NOT NULL THEN DATEDIFF(b.lost_date, b.deal_date) ELSE 0 END) as sum_failure_days_old,
                SUM(CASE WHEN b.lead_type = 'old' AND b.deal_status_group = 'lost' AND b.lost_date IS NOT NULL THEN 1 ELSE 0 END) as count_failure_days_old,
                SUM(CASE WHEN b.has_payment = 1 AND b.first_payment_date IS NOT NULL AND DATEDIFF(b.first_payment_date, b.deal_date) > 0 THEN 1 ELSE 0 END) as payments_after_deal_count,
                SUM(CASE WHEN b.has_payment = 1 AND b.first_payment_date IS NOT NULL AND DATEDIFF(b.first_payment_date, b.deal_date) < 0 THEN 1 ELSE 0 END) as payments_before_deal_count,
                SUM(CASE WHEN b.has_payment = 1 AND b.first_payment_date IS NOT NULL AND DATEDIFF(b.first_payment_date, b.deal_date) = 0 THEN 1 ELSE 0 END) as payments_same_day_count
            FROM ({$dealsBaseSql}) b
            GROUP BY b.deal_date, b.owner_didar_id, b.owner_name
            ORDER BY b.deal_date ASC, b.owner_name ASC
        ", $paramsDeals);

        $tableFailureReasons = $this->runSelectAll("
            SELECT
                b.deal_date as date,
                b.owner_didar_id,
                b.owner_name,
                b.failure_reason,
                SUM(CASE WHEN b.deal_status_group = 'lost' AND b.has_open_deal = 0 THEN 1 ELSE 0 END) as failed_without_current_count,
                SUM(CASE WHEN b.deal_status_group = 'lost' AND b.lead_type = 'new' THEN 1 ELSE 0 END) as failed_new_count,
                SUM(CASE WHEN b.deal_status_group = 'lost' AND b.lead_type = 'old' THEN 1 ELSE 0 END) as failed_old_count
            FROM ({$dealsBaseSql}) b
            WHERE b.deal_status_group = 'lost'
            GROUP BY b.deal_date, b.owner_didar_id, b.owner_name, b.failure_reason
            ORDER BY b.deal_date ASC, b.owner_name ASC, b.failure_reason ASC
        ", $paramsDeals);

        $resolvedProductExpr = $this->buildResolvedProductNameExpr('b.deal_product_hint', 'pr', 'dp');
        $primaryDealProductSql = $this->buildPrimaryDealProductSql();

        $tableProductStatus = $this->runSelectAll("
            SELECT
                b.deal_date as date,
                b.owner_didar_id,
                b.owner_name,
                {$resolvedProductExpr} as product_name,
                COUNT(DISTINCT dp.deal_id) as sales_total_count,
                COUNT(DISTINCT CASE WHEN b.lead_type = 'new' THEN dp.deal_id END) as sales_new_count,
                COUNT(DISTINCT CASE WHEN b.lead_type = 'old' THEN dp.deal_id END) as sales_old_count
            FROM ({$dealsBaseSql}) b
            INNER JOIN ({$primaryDealProductSql}) dp ON dp.deal_id = b.deal_id
            LEFT JOIN products pr ON pr.id = dp.product_id
            WHERE b.has_payment = 1
            GROUP BY b.deal_date, b.owner_didar_id, b.owner_name, {$resolvedProductExpr}
            ORDER BY b.deal_date ASC, b.owner_name ASC, product_name ASC
        ", $paramsDeals);

        $tableProductSoldByOwnerDay = $this->runSelectAll("
            SELECT
                b.deal_date as date,
                b.owner_didar_id,
                b.owner_name,
                {$resolvedProductExpr} as product_name,
                COUNT(DISTINCT CASE WHEN b.lead_type = 'new' THEN b.deal_id END) as sold_new_count,
                COUNT(DISTINCT CASE WHEN b.lead_type = 'old' THEN b.deal_id END) as sold_old_count,
                COUNT(DISTINCT b.deal_id) as sold_total_count
            FROM ({$dealsBaseSql}) b
            INNER JOIN ({$primaryDealProductSql}) dp ON dp.deal_id = b.deal_id
            LEFT JOIN products pr ON pr.id = dp.product_id
            WHERE b.has_payment = 1
            GROUP BY b.deal_date, b.owner_didar_id, b.owner_name, {$resolvedProductExpr}
            ORDER BY b.deal_date ASC, b.owner_name ASC, product_name ASC
        ", $paramsDeals);

        $tableProductRequestsByOwnerDay = $this->runSelectAll("
            SELECT
                b.deal_date as date,
                b.owner_didar_id,
                b.owner_name,
                {$resolvedProductExpr} as product_name,
                COUNT(DISTINCT CASE WHEN b.lead_type = 'new' THEN b.deal_id END) as requested_new_count,
                COUNT(DISTINCT CASE WHEN b.lead_type = 'old' THEN b.deal_id END) as requested_old_count,
                COUNT(DISTINCT b.deal_id) as requested_total_count,
                COUNT(DISTINCT CASE WHEN b.has_payment = 1 THEN b.deal_id END) as sold_total_count,
                COUNT(DISTINCT CASE WHEN b.has_payment = 0 THEN b.deal_id END) as unsold_total_count
            FROM ({$dealsBaseSql}) b
            INNER JOIN ({$primaryDealProductSql}) dp ON dp.deal_id = b.deal_id
            LEFT JOIN products pr ON pr.id = dp.product_id
            GROUP BY b.deal_date, b.owner_didar_id, b.owner_name, {$resolvedProductExpr}
            ORDER BY b.deal_date ASC, b.owner_name ASC, product_name ASC
        ", $paramsDeals);

        $tableProductFailedByOwnerDay = $this->runSelectAll("
            SELECT
                b.deal_date as date,
                b.owner_didar_id,
                b.owner_name,
                {$resolvedProductExpr} as product_name,
                COUNT(DISTINCT CASE WHEN b.lead_type = 'new' THEN b.deal_id END) as failed_new_count,
                COUNT(DISTINCT CASE WHEN b.lead_type = 'old' THEN b.deal_id END) as failed_old_count,
                COUNT(DISTINCT b.deal_id) as failed_total_count
            FROM ({$dealsBaseSql}) b
            INNER JOIN ({$primaryDealProductSql}) dp ON dp.deal_id = b.deal_id
            LEFT JOIN products pr ON pr.id = dp.product_id
            WHERE b.has_payment = 0
            GROUP BY b.deal_date, b.owner_didar_id, b.owner_name, {$resolvedProductExpr}
            ORDER BY b.deal_date ASC, b.owner_name ASC, product_name ASC
        ", $paramsDeals);

        $tableActivitySummary = [];
        $tableActivityTypeDetails = [];
        if (!$skipActivityTables) {
            $activityTypeExpr = $this->buildCanonicalActivityTypeExpr('a');
            $activityOwnerWhereAct = '';
            $activityOwnerWherePersons = '';
            $activityOwnerWhereDeals = '';
            $activityOwnerParamsAct = [];
            $activityOwnerParamsPersons = [];
            $activityOwnerParamsDeals = [];

            if (!empty($filters['owner_didar_ids_csv'])) {
                $activityOwnerWhereAct = ' AND FIND_IN_SET(a.owner_didar_id, ?)';
                $activityOwnerWherePersons = ' AND FIND_IN_SET(p.owner_didar_id, ?)';
                $activityOwnerWhereDeals = ' AND FIND_IN_SET(d.owner_didar_id, ?)';
                $activityOwnerParamsAct = [$filters['owner_didar_ids_csv']];
                $activityOwnerParamsPersons = [$filters['owner_didar_ids_csv']];
                $activityOwnerParamsDeals = [$filters['owner_didar_ids_csv']];
            } elseif (!empty($filters['owner_didar_id']) && trim((string)$filters['owner_didar_id']) !== '') {
                $ownerDidarId = trim((string)$filters['owner_didar_id']);
                $activityOwnerWhereAct = ' AND a.owner_didar_id = ?';
                $activityOwnerWherePersons = ' AND p.owner_didar_id = ?';
                $activityOwnerWhereDeals = ' AND d.owner_didar_id = ?';
                $activityOwnerParamsAct = [$ownerDidarId];
                $activityOwnerParamsPersons = [$ownerDidarId];
                $activityOwnerParamsDeals = [$ownerDidarId];
            }

            $activitySummaryParams = array_merge(
                [$dateFrom, $dateTo],
                $activityOwnerParamsAct,
                [$dateFrom, $dateTo],
                $activityOwnerParamsPersons,
                [$dateFrom, $dateTo],
                $activityOwnerParamsDeals
            );

            $tableActivitySummary = $this->runSelectAll("
            SELECT
                b.activity_date as date,
                b.owner_didar_id,
                b.owner_name,
                b.total_activities,
                b.total_duration_minutes,
                b.avg_duration_minutes,
                b.activities_new_lead,
                COALESCE(n.new_leads_without_activity, 0) as new_leads_without_activity,
                b.activities_old_lead,
                COALESCE(o.old_leads_without_activity, 0) as old_leads_without_activity,
                b.no_response_new,
                b.no_response_old,
                (b.no_response_new + b.no_response_old) as no_response_total
            FROM (
                SELECT
                    DATE(COALESCE(a.register_date, a.created_at)) as activity_date,
                    COALESCE(a.owner_didar_id, '') as owner_didar_id,
                    COALESCE(u.display_name, 'بدون مسئول') as owner_name,
                    COUNT(*) as total_activities,
                    SUM(COALESCE(a.duration, 0)) as total_duration_minutes,
                    ROUND(AVG(CASE WHEN COALESCE(a.duration, 0) > 0 THEN a.duration END), 2) as avg_duration_minutes,
                    SUM(CASE WHEN DATE(COALESCE(p.register_time, p.created_at)) = DATE(COALESCE(a.register_date, a.created_at)) THEN 1 ELSE 0 END) as activities_new_lead,
                    SUM(CASE WHEN DATE(COALESCE(p.register_time, p.created_at)) < DATE(COALESCE(a.register_date, a.created_at)) THEN 1 ELSE 0 END) as activities_old_lead,
                    SUM(CASE
                        WHEN (
                            {$activityTypeExpr} = 'تماس عدم پاسخ'
                        )
                        AND DATE(COALESCE(p.register_time, p.created_at)) = DATE(COALESCE(a.register_date, a.created_at))
                        THEN 1 ELSE 0 END
                    ) as no_response_new,
                    SUM(CASE
                        WHEN (
                            {$activityTypeExpr} = 'تماس عدم پاسخ'
                        )
                        AND DATE(COALESCE(p.register_time, p.created_at)) < DATE(COALESCE(a.register_date, a.created_at))
                        THEN 1 ELSE 0 END
                    ) as no_response_old
                FROM activities a
                LEFT JOIN persons p ON p.didar_contact_id = a.contact_didar_id
                LEFT JOIN users u ON u.didar_user_id = a.owner_didar_id
                WHERE DATE(COALESCE(a.register_date, a.created_at)) BETWEEN ? AND ?
                  {$activityOwnerWhereAct}
                GROUP BY DATE(COALESCE(a.register_date, a.created_at)), COALESCE(a.owner_didar_id, ''), COALESCE(u.display_name, 'بدون مسئول')
            ) b
            LEFT JOIN (
                SELECT
                    DATE(COALESCE(p.register_time, p.created_at)) as activity_date,
                    COALESCE(p.owner_didar_id, '') as owner_didar_id,
                    COUNT(DISTINCT p.didar_contact_id) as new_leads_without_activity
                FROM persons p
                LEFT JOIN activities a
                    ON a.contact_didar_id = p.didar_contact_id
                   AND DATE(COALESCE(a.register_date, a.created_at)) = DATE(COALESCE(p.register_time, p.created_at))
                WHERE DATE(COALESCE(p.register_time, p.created_at)) BETWEEN ? AND ?
                  {$activityOwnerWherePersons}
                  AND a.id IS NULL
                GROUP BY DATE(COALESCE(p.register_time, p.created_at)), COALESCE(p.owner_didar_id, '')
            ) n ON n.activity_date = b.activity_date AND n.owner_didar_id = b.owner_didar_id
            LEFT JOIN (
                SELECT
                    DATE(COALESCE(d.register_time, d.created_at)) as activity_date,
                    COALESCE(d.owner_didar_id, '') as owner_didar_id,
                    COUNT(DISTINCT p.didar_contact_id) as old_leads_without_activity
                FROM deals d
                INNER JOIN persons p ON p.didar_contact_id = d.contact_didar_id
                LEFT JOIN activities a
                    ON a.contact_didar_id = p.didar_contact_id
                   AND DATE(COALESCE(a.register_date, a.created_at)) = DATE(COALESCE(d.register_time, d.created_at))
                WHERE DATE(COALESCE(d.register_time, d.created_at)) BETWEEN ? AND ?
                  AND DATE(COALESCE(p.register_time, p.created_at)) < DATE(COALESCE(d.register_time, d.created_at))
                  {$activityOwnerWhereDeals}
                  AND a.id IS NULL
                  {$legacyDealsClauseForD}
                GROUP BY DATE(COALESCE(d.register_time, d.created_at)), COALESCE(d.owner_didar_id, '')
            ) o ON o.activity_date = b.activity_date AND o.owner_didar_id = b.owner_didar_id
            ORDER BY b.activity_date ASC, b.owner_name ASC
        ", $activitySummaryParams);

            $tableActivityTypeDetails = $this->runSelectAll("
            SELECT
                t.activity_date as date,
                t.owner_didar_id,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name,
                t.activity_type,
                t.activity_type_new_count,
                t.activity_type_old_count,
                t.activity_count_total,
                t.activity_type_duration_minutes,
                ROUND((t.activity_type_duration_minutes / NULLIF(t.activity_count_total, 0)), 2) as avg_activity_type_duration_minutes,
                ROUND((t.activity_count_total / NULLIF(d.total_activities, 0)) * 100, 2) as activity_share_percent
            FROM (
                SELECT
                    DATE(COALESCE(a.register_date, a.created_at)) as activity_date,
                    COALESCE(a.owner_didar_id, '') as owner_didar_id,
                    {$activityTypeExpr} as activity_type,
                    SUM(CASE WHEN DATE(COALESCE(p.register_time, p.created_at)) = DATE(COALESCE(a.register_date, a.created_at)) THEN 1 ELSE 0 END) as activity_type_new_count,
                    SUM(CASE WHEN DATE(COALESCE(p.register_time, p.created_at)) < DATE(COALESCE(a.register_date, a.created_at)) THEN 1 ELSE 0 END) as activity_type_old_count,
                    COUNT(*) as activity_count_total,
                    SUM(COALESCE(a.duration, 0)) as activity_type_duration_minutes
                FROM activities a
                LEFT JOIN persons p ON p.didar_contact_id = a.contact_didar_id
                WHERE DATE(COALESCE(a.register_date, a.created_at)) BETWEEN ? AND ?
                  {$activityOwnerWhereAct}
                GROUP BY DATE(COALESCE(a.register_date, a.created_at)), COALESCE(a.owner_didar_id, ''), {$activityTypeExpr}
            ) t
            LEFT JOIN users u ON u.didar_user_id = t.owner_didar_id
            INNER JOIN (
                SELECT
                    DATE(COALESCE(a.register_date, a.created_at)) as activity_date,
                    COALESCE(a.owner_didar_id, '') as owner_didar_id,
                    COUNT(*) as total_activities
                FROM activities a
                WHERE DATE(COALESCE(a.register_date, a.created_at)) BETWEEN ? AND ?
                  {$activityOwnerWhereAct}
                GROUP BY DATE(COALESCE(a.register_date, a.created_at)), COALESCE(a.owner_didar_id, '')
            ) d ON d.activity_date = t.activity_date AND d.owner_didar_id = t.owner_didar_id
            ORDER BY t.activity_date ASC, owner_name ASC, t.activity_type ASC
        ", array_merge(
                [$dateFrom, $dateTo],
                $activityOwnerParamsAct,
                [$dateFrom, $dateTo],
                $activityOwnerParamsAct
            ));
        }

        $tableSummaryByUser = $this->runSelectAll("
            SELECT
                b.owner_didar_id,
                b.owner_name,
                COUNT(*) as total_deals_count,
                SUM(CASE WHEN b.has_payment = 1 THEN 1 ELSE 0 END) as sales_total_count,
                ROUND((SUM(CASE WHEN b.has_payment = 1 THEN 1 ELSE 0 END) / NULLIF(COUNT(*), 0)) * 100, 2) as conversion_rate_total,
                SUM(CASE WHEN b.lead_type = 'new' THEN 1 ELSE 0 END) as total_new_deals_count,
                SUM(CASE WHEN b.lead_type = 'new' AND b.has_payment = 1 THEN 1 ELSE 0 END) as sales_new_count,
                ROUND((SUM(CASE WHEN b.lead_type = 'new' AND b.has_payment = 1 THEN 1 ELSE 0 END) / NULLIF(SUM(CASE WHEN b.lead_type = 'new' THEN 1 ELSE 0 END), 0)) * 100, 2) as conversion_rate_new,
                SUM(CASE WHEN b.has_payment = 1 THEN b.total_paid ELSE 0 END) as total_collected_amount,
                SUM(CASE WHEN b.has_payment = 1 AND b.payable_amount > 0 AND b.total_paid < b.payable_amount THEN (b.payable_amount - b.total_paid) ELSE 0 END) as outstanding_amount_sum
            FROM ({$dealsBaseSql}) b
            GROUP BY b.owner_didar_id, b.owner_name
            ORDER BY conversion_rate_total DESC, sales_total_count DESC
        ", $paramsDeals);

        $result = [
            'deal_status' => $tableDealStatus,
            'lead_status' => $tableLeadStatus,
            'payment_mix' => $tablePaymentMix,
            'payment_status' => $tablePaymentStatus,
            'summary_by_user' => $tableSummaryByUser,
            'conversion_status' => $tableConversionStatus,
            'conversion_time' => $tableConversionTime,
            'failure_reasons' => $tableFailureReasons,
            'product_status' => $tableProductStatus,
            'product_sold_by_owner_day' => $tableProductSoldByOwnerDay,
            'product_requests_by_owner_day' => $tableProductRequestsByOwnerDay,
            'product_failed_by_owner_day' => $tableProductFailedByOwnerDay,
            'activity_summary' => $tableActivitySummary,
            'activity_type_details' => $tableActivityTypeDetails
        ];

        if ($this->getKpiPaymentDateBasis($filters, 'sales') === 'ui_payment_date') {
            $result = $this->applyUiOracleAdvancedKpiOverrides($filters, $result);
        }

        return $result;
    }

    /**
     * داشبورد عملکرد (خروجی نرمال‌شده برای UI اختصاصی عملکرد)
     */
    public function getPerformanceDashboardData(array $filters): array
    {
        $this->ensureActivityTypeBackfill();
        $skipCharts = !empty($filters['_perf_skip_charts']);
        $skipTabs = !empty($filters['_perf_skip_tabs']);
        $skipRaw = !empty($filters['_perf_skip_raw']);

        $sales = $this->getSales($filters);
        $settled = $this->getSettledDeals($filters);
        $outstanding = $this->getOutstandingBalance($filters);
        $salesWindowClassified = $this->getPaymentWindowDealClassification($filters);
        $lost = $this->getLostSales($filters);
        $openPipeline = $this->getOpenPipeline($filters);
        $withoutPayments = $this->getDealsWithoutPayments($filters);
        $productSales = (!$skipCharts || !$skipRaw) ? $this->getProductSalesCounts($filters) : [];
        $dealConversion = $this->getDealConversionRate($filters);
        $leadConversion = $this->getLeadConversionRate($filters);
        $dailySales = $skipCharts ? [] : $this->getDailySales($filters);
        $dailyActivitiesByType = $skipCharts ? [] : $this->getDailyActivitiesByType($filters);
        $dailyOperational = ($skipCharts && $skipRaw)
            ? ['overall_daily' => [], 'by_expert_daily' => []]
            : $this->getDailyOperationalKpis($filters);
        $advanced = $skipTabs ? [
            'deal_status' => [],
            'lead_status' => [],
            'payment_mix' => [],
            'payment_status' => [],
            'summary_by_user' => [],
            'conversion_status' => [],
            'conversion_time' => [],
            'failure_reasons' => [],
            'product_status' => [],
            'product_sold_by_owner_day' => [],
            'product_requests_by_owner_day' => [],
            'product_failed_by_owner_day' => [],
            'activity_summary' => [],
            'activity_type_details' => [],
        ] : $this->getAdvancedKpiTables($filters);
        $uiOracleForPerformance = null;
        if (
            $this->getKpiPaymentDateBasis($filters, 'sales') === 'ui_payment_date'
            || $this->getKpiPaymentDateBasis($filters, 'payment') === 'ui_payment_date'
        ) {
            $uiOracleForPerformance = $this->getUiOracleKpiData($filters);
        }
        $performancePaymentRangeSplit = $this->getPerformancePaymentRangeSplit($filters);
        $activityPerformance = !empty($filters['_perf_skip_activity_performance'])
            ? ['past_deals' => [], 'daily' => []]
            : $this->buildActivityPerformanceSnapshot($filters);
        if (!empty($uiOracleForPerformance)) {
            $lostReasonBreakdown = $this->buildLostReasonBreakdownFromOracle($uiOracleForPerformance);
        } else {
            $lostReasonBreakdown = array_values(array_filter(array_map(static function ($row) {
                $count = (int)($row['total_lost'] ?? 0);
                $reason = trim((string)($row['failure_reason_text'] ?? ''));
                if ($count <= 0 || $reason === '') {
                    return null;
                }
                return [
                    'reason' => $reason,
                    'count' => $count,
                    'total_amount' => (float)($row['total_amount'] ?? 0),
                ];
            }, $lost)));
            usort($lostReasonBreakdown, static function ($a, $b) {
                return ((int)($b['count'] ?? 0)) <=> ((int)($a['count'] ?? 0));
            });
        }

        $activityDaily = [];
        if (!$skipCharts) {
            foreach (($dailyOperational['overall_daily'] ?? []) as $row) {
                $activityDaily[] = [
                    'date' => $row['date'] ?? '',
                    'activities_count' => (int)($row['activities_count'] ?? 0)
                ];
            }
        }

        $dealStatusBreakdown = [];
        if (empty($filters['_perf_disable_chart_deal_status'])) {
            $dealStatusBreakdown = [
                ['status' => 'جاری', 'count' => 0],
                ['status' => 'دارای فروش', 'count' => 0],
                ['status' => 'بدون فروش', 'count' => 0],
                ['status' => 'ناموفق', 'count' => 0],
            ];
            foreach (($advanced['deal_status'] ?? []) as $row) {
                $dealStatusBreakdown[0]['count'] += (int)($row['deal_pending_total'] ?? 0);
                $dealStatusBreakdown[1]['count'] += (int)($row['deal_with_sales_total'] ?? 0);
                $dealStatusBreakdown[2]['count'] += (int)($row['deal_without_sales_total'] ?? 0);
                $dealStatusBreakdown[3]['count'] += (int)($row['deal_lost_total'] ?? 0);
            }
        }

        $productSalesShare = [];
        if (!$skipCharts && !empty($advanced['product_status'])) {
            $productCounts = [];
            foreach (($advanced['product_status'] ?? []) as $row) {
                $name = trim((string)($row['product_name'] ?? ''));
                if ($name === '') {
                    continue;
                }
                $productCounts[$name] = ($productCounts[$name] ?? 0) + (int)($row['sales_total_count'] ?? 0);
            }
            arsort($productCounts);
            foreach ($productCounts as $name => $count) {
                $productSalesShare[] = ['product_name' => $name, 'deal_count' => (int)$count];
            }
        } elseif (!$skipCharts) {
            $productSalesShare = array_map(static function ($row) {
                return [
                    'product_name' => $row['product_name'] ?? 'نامشخص',
                    'deal_count' => (int)($row['deal_count'] ?? 0)
                ];
            }, $productSales);
        }

        return [
            'summary_cards' => [
                'total_sales_amount' => (float)($uiOracleForPerformance['sales_card']['total_collected_amount'] ?? ($sales['total_collected_amount'] ?? 0)),
                'total_contract_amount' => (float)($uiOracleForPerformance['sales_card']['total_contract_amount'] ?? ($sales['total_contract_amount'] ?? ($salesWindowClassified['paid_deals_contract_sum'] ?? 0))),
                'with_sales_deals_count' => (int)($uiOracleForPerformance['sales_card']['deal_count'] ?? ($sales['deal_count'] ?? ($salesWindowClassified['paid_deals_count'] ?? 0))),
                'settled_count' => (int)($uiOracleForPerformance['financial']['settled_count'] ?? ($salesWindowClassified['settled_count'] ?? ($settled['deal_count'] ?? 0))),
                'settled_amount' => (float)($uiOracleForPerformance['financial']['settled_contract_sum'] ?? ($salesWindowClassified['settled_contract_sum'] ?? ($settled['total_amount'] ?? 0))),
                'settled_collected_amount' => (float)($uiOracleForPerformance['financial']['settled_collected_sum'] ?? ($salesWindowClassified['settled_collected_sum'] ?? ($settled['total_collected_amount'] ?? 0))),
                'outstanding_count' => (int)($uiOracleForPerformance['financial']['outstanding_count'] ?? ($salesWindowClassified['outstanding_count'] ?? ($outstanding['deal_count'] ?? 0))),
                'outstanding_amount' => (float)($uiOracleForPerformance['financial']['outstanding_amount_sum'] ?? ($salesWindowClassified['outstanding_amount_sum'] ?? ($outstanding['outstanding_amount'] ?? 0))),
                'outstanding_contract_amount' => (float)($uiOracleForPerformance['financial']['outstanding_contract_sum'] ?? ($salesWindowClassified['outstanding_contract_sum'] ?? ($outstanding['total_contract_amount'] ?? 0))),
                'outstanding_collected_amount' => (float)($uiOracleForPerformance['financial']['outstanding_collected_sum'] ?? ($salesWindowClassified['outstanding_collected_sum'] ?? ($outstanding['total_collected'] ?? 0))),
                'without_payment_count' => (int)($uiOracleForPerformance['conversion_partition']['without_payment_count'] ?? ($withoutPayments['deal_count'] ?? 0)),
                'without_payment_amount' => (float)($uiOracleForPerformance['conversion_partition']['without_payment_amount'] ?? ($withoutPayments['total_potential_amount'] ?? 0)),
                'open_pipeline_count' => (int)($uiOracleForPerformance['conversion_partition']['open_valid_count'] ?? ($openPipeline['deal_count'] ?? 0)),
                'open_pipeline_amount' => (float)($uiOracleForPerformance['conversion_partition']['open_valid_amount'] ?? ($openPipeline['total_potential_amount'] ?? 0)),
                'won_count' => (int)($uiOracleForPerformance['conversion_partition']['won_count'] ?? 0),
                'won_amount' => (float)($uiOracleForPerformance['conversion_partition']['won_amount'] ?? 0),
                'deal_conversion_rate' => (float)($dealConversion['conversion_rate'] ?? 0),
                'deal_non_conversion_rate' => (float)($dealConversion['non_conversion_rate'] ?? 0),
                'deal_conversion_total_deals' => (int)($dealConversion['total_deals'] ?? 0),
                'deal_related_total_count' => (int)($uiOracleForPerformance['deal_related_split']['total_count'] ?? ($dealConversion['total_deals'] ?? 0)),
                'deal_related_total_amount' => (float)($uiOracleForPerformance['deal_related_split']['total_amount'] ?? 0),
                'deal_registered_in_range_count' => (int)($uiOracleForPerformance['deal_related_split']['registered_in_range_count'] ?? 0),
                'deal_registered_in_range_amount' => (float)($uiOracleForPerformance['deal_related_split']['registered_in_range_amount'] ?? 0),
                'deal_outside_registered_first_payment_count' => (int)($uiOracleForPerformance['deal_related_split']['outside_registered_first_payment_count'] ?? 0),
                'deal_outside_registered_first_payment_amount' => (float)($uiOracleForPerformance['deal_related_split']['outside_registered_first_payment_amount'] ?? 0),
                'payment_related_total_amount' => (float)($performancePaymentRangeSplit['total']['amount'] ?? ($uiOracleForPerformance['deposit_related_split']['total_amount'] ?? ($sales['total_collected_amount'] ?? 0))),
                'payment_related_total_deal_count' => (int)($performancePaymentRangeSplit['total']['deal_count'] ?? ($uiOracleForPerformance['deposit_related_split']['total_deal_count'] ?? ($sales['deal_count'] ?? 0))),
                'payment_related_total_receipt_count' => (int)($performancePaymentRangeSplit['total']['payment_count'] ?? ($uiOracleForPerformance['deposit_related_split']['total_payment_count'] ?? 0)),
                'payment_registered_in_range_amount' => (float)($performancePaymentRangeSplit['registered_in_range']['amount'] ?? ($uiOracleForPerformance['deposit_related_split']['registered_in_range_amount'] ?? 0)),
                'payment_registered_in_range_deal_count' => (int)($performancePaymentRangeSplit['registered_in_range']['deal_count'] ?? ($uiOracleForPerformance['deposit_related_split']['registered_in_range_deal_count'] ?? 0)),
                'payment_registered_in_range_receipt_count' => (int)($performancePaymentRangeSplit['registered_in_range']['payment_count'] ?? ($uiOracleForPerformance['deposit_related_split']['registered_in_range_payment_count'] ?? 0)),
                'payment_outside_registered_first_payment_amount' => (float)($performancePaymentRangeSplit['outside_registered']['amount'] ?? ($uiOracleForPerformance['deposit_related_split']['outside_registered_first_payment_amount'] ?? 0)),
                'payment_outside_registered_first_payment_deal_count' => (int)($performancePaymentRangeSplit['outside_registered']['deal_count'] ?? ($uiOracleForPerformance['deposit_related_split']['outside_registered_first_payment_deal_count'] ?? 0)),
                'payment_outside_registered_first_payment_receipt_count' => (int)($performancePaymentRangeSplit['outside_registered']['payment_count'] ?? ($uiOracleForPerformance['deposit_related_split']['outside_registered_first_payment_payment_count'] ?? 0)),
                'lost_deposit_amount' => (float)($performancePaymentRangeSplit['categories']['lost_deposit']['amount'] ?? ($uiOracleForPerformance['deposit_category_split']['lost_deposit']['amount'] ?? 0)),
                'lost_deposit_deal_count' => (int)($performancePaymentRangeSplit['categories']['lost_deposit']['deal_count'] ?? ($uiOracleForPerformance['deposit_category_split']['lost_deposit']['deal_count'] ?? 0)),
                'lost_deposit_receipt_count' => (int)($performancePaymentRangeSplit['categories']['lost_deposit']['payment_count'] ?? ($uiOracleForPerformance['deposit_category_split']['lost_deposit']['payment_count'] ?? 0)),
                'lead_conversion_rate' => (float)($leadConversion['conversion_rate'] ?? 0),
                'lead_non_conversion_rate' => (float)($leadConversion['non_conversion_rate'] ?? 0),
                'lead_total_new' => (int)($leadConversion['total_new_leads'] ?? 0),
                'lead_converted' => (int)($leadConversion['converted_leads'] ?? 0),
                'avg_conversion_days' => (float)($this->getTimeToConversion($filters)['avg_conversion_days'] ?? 0),
                'lost_count' => (int)($uiOracleForPerformance['conversion_partition']['lost_count'] ?? array_sum(array_map(static function ($r) { return (int)($r['total_lost'] ?? 0); }, $lost))),
                'lost_amount' => (float)($uiOracleForPerformance['conversion_partition']['lost_amount'] ?? array_sum(array_map(static function ($r) { return (float)($r['total_amount'] ?? 0); }, $lost))),
                'lost_reason_breakdown' => $lostReasonBreakdown,
                'activity_performance' => $activityPerformance,
            ],
            'charts' => $skipCharts ? [] : [
                'daily_sales' => $dailySales,
                'daily_activities' => $activityDaily,
                'daily_activities_by_type' => $dailyActivitiesByType,
                'deal_status_breakdown' => $dealStatusBreakdown,
                'product_sales_share' => $productSalesShare
            ],
            'tabs' => $skipTabs ? [] : [
                'activity' => [
                    'activity_summary' => $advanced['activity_summary'] ?? [],
                    'activity_type_details' => $advanced['activity_type_details'] ?? []
                ],
                'sales_deals' => [
                    'deal_status' => $advanced['deal_status'] ?? [],
                    'lead_status' => $advanced['lead_status'] ?? [],
                    'conversion_status' => $advanced['conversion_status'] ?? []
                ],
                'payments' => [
                    'payment_mix' => $advanced['payment_mix'] ?? [],
                    'payment_status' => $advanced['payment_status'] ?? []
                ],
                'products' => [
                    'product_status' => $advanced['product_status'] ?? [],
                    'product_sold_by_owner_day' => $advanced['product_sold_by_owner_day'] ?? [],
                    'product_requests_by_owner_day' => $advanced['product_requests_by_owner_day'] ?? [],
                    'product_failed_by_owner_day' => $advanced['product_failed_by_owner_day'] ?? []
                ],
                'failure_reasons' => [
                    'failure_reasons' => $advanced['failure_reasons'] ?? []
                ]
            ],
            'raw' => $skipRaw ? [] : [
                'daily_operational' => $dailyOperational,
                'advanced_tables' => $advanced,
                'product_sales' => $productSales,
                'lost_sales' => $lost,
                'activity_performance' => $activityPerformance,
            ]
        ];
    }

    public function getCeoPerformanceReport(array $filters): array
    {
        if (!$this->tableHasColumns('payment_records', ['id', 'deal_id', 'amount'])) {
            return $this->emptyCeoPerformanceReport();
        }

        $paymentRows = $this->getCeoPaymentRows($filters);
        $summary = $this->emptyCeoSummary();
        $cashRows = [
            'sales_deposit' => $this->ceoInitAggregateRow('sales_deposit', 'واریزی فروش'),
            'balance_deposit' => $this->ceoInitAggregateRow('balance_deposit', 'واریزی مانده حساب'),
            'cheque_collected' => $this->ceoInitAggregateRow('cheque_collected', 'واریزی چک‌ها'),
            'sudran_deposit' => $this->ceoInitAggregateRow('sudran_deposit', 'پرداخت‌های سودران'),
            'lost_deposit' => $this->ceoInitAggregateRow('lost_deposit', 'واریزی معاملات ناموفق'),
        ];
        $totalDeposits = $this->ceoInitAggregateRow('total_deposits', 'کل واریزی‌ها');
        $marketingRows = [];
        $marketingDeals = [];
        $marketingTotal = $this->ceoInitAggregateRow('marketing_total', 'کل پرداخت‌های بازاریابی');
        $paymentDealRangeSplit = [
            'registered_range_deposit' => $this->ceoInitAggregateRow('registered_range_deposit', 'پرداخت معاملات ثبت‌شده در بازه'),
            'outside_range_deposit' => $this->ceoInitAggregateRow('outside_range_deposit', 'پرداخت معاملات خارج از بازه'),
        ];
        $agentRows = [];
        $agentDeals = [];
        $agentSalesDeals = [];
        $agentLeadSourceDeals = [];
        $agentTotal = array_merge([
            'sales_deal_count' => 0,
            'payment_count' => 0,
            'payment_amount' => 0.0,
            'sales_amount' => 0.0,
            'deal_ids' => [],
            'sales_deal_ids' => [],
            'lead_source_deals' => $this->ceoInitLeadSourceDealBuckets(),
        ], $this->ceoInitSourcePaymentAmounts());

        foreach ($paymentRows as $row) {
            $category = $this->classifyCeoPaymentRow($row, $filters);
            if ($category === '') {
                continue;
            }
            $amount = (float)($row['net_amount'] ?? 0);
            $dealId = (int)($row['deal_id'] ?? 0);
            if (!isset($cashRows[$category])) {
                continue;
            }
            $this->ceoAddPaymentToAggregate($cashRows[$category], $row);
            $this->ceoAddPaymentToAggregate($totalDeposits, $row);
            $dealRegisteredDate = trim((string)($row['deal_registered_date'] ?? ''));
            $rangeBucket = $this->ceoDateInFilterRange($dealRegisteredDate, $filters) ? 'registered_range_deposit' : 'outside_range_deposit';
            $this->ceoAddPaymentToAggregate($paymentDealRangeSplit[$rangeBucket], $row);
            $ownerKey = trim((string)($row['owner_didar_id'] ?? ''));
            if ($ownerKey === '') {
                $ownerKey = 'name:' . md5(trim((string)($row['owner_name'] ?? 'بدون مسئول')));
            }
            if (!isset($agentRows[$ownerKey])) {
                $agentRows[$ownerKey] = array_merge([
                    'key' => $ownerKey,
                    'owner_didar_id' => trim((string)($row['owner_didar_id'] ?? '')),
                    'owner_name' => trim((string)($row['owner_name'] ?? 'بدون مسئول')) ?: 'بدون مسئول',
                    'sales_deal_count' => 0,
                    'deal_count' => 0,
                    'payment_count' => 0,
                    'sales_amount' => 0.0,
                    'payment_amount' => 0.0,
                    'crm_lead_count' => 0,
                    'crm_lead_share_percent' => 0.0,
                    'personal_lead_count' => 0,
                    'personal_lead_share_percent' => 0.0,
                    'sudran_lead_count' => 0,
                    'sudran_lead_share_percent' => 0.0,
                    'sales_count_share_percent' => 0.0,
                    'sales_amount_share_percent' => 0.0,
                    'payment_count_share_percent' => 0.0,
                    'payment_amount_share_percent' => 0.0,
                ], $this->ceoInitSourcePaymentAmounts());
                $agentLeadSourceDeals[$ownerKey] = $this->ceoInitLeadSourceDealBuckets();
            }
            $agentRows[$ownerKey]['payment_count']++;
            $agentRows[$ownerKey]['payment_amount'] += $amount;
            $agentTotal['payment_count']++;
            $agentTotal['payment_amount'] += $amount;
            if ($category === 'sales_deposit') {
                $agentRows[$ownerKey]['sales_amount'] += $amount;
                $agentTotal['sales_amount'] += $amount;
            }
            $leadSource = $this->ceoLeadSourceBucket($row, $category);
            $this->ceoAddSourcePaymentAmount($agentRows[$ownerKey], $leadSource, $category, $amount);
            $this->ceoAddSourcePaymentAmount($agentTotal, $leadSource, $category, $amount);
            if ($dealId > 0) {
                $agentDeals[$ownerKey][$dealId] = true;
                $agentTotal['deal_ids'][$dealId] = true;
                $this->ceoMarkLeadSourceDeal($agentLeadSourceDeals[$ownerKey], $leadSource, $dealId);
                $this->ceoMarkLeadSourceDeal($agentTotal['lead_source_deals'], $leadSource, $dealId);
                if ($category === 'sales_deposit') {
                    $agentSalesDeals[$ownerKey][$dealId] = true;
                    $agentTotal['sales_deal_ids'][$dealId] = true;
                }
            }

            $source = $this->canonicalCeoMarketingLabel('source', (string)($row['acquaintance_source'] ?? ''));
            $detail = $this->canonicalCeoMarketingLabel('detail', (string)($row['acquaintance_detail'] ?? ''));
            $topic = $this->canonicalCeoMarketingLabel('topic', (string)($row['content_topic'] ?? ''));
            $marketingKey = md5($source . '|' . $detail . '|' . $topic);
            if (!isset($marketingRows[$marketingKey])) {
                $marketingRows[$marketingKey] = [
                    'key' => $marketingKey,
                    'acquaintance_source' => $source,
                    'acquaintance_detail' => $detail,
                    'content_topic' => $topic,
                    'payment_count' => 0,
                    'deal_count' => 0,
                    'amount' => 0.0,
                    'payment_share_percent' => 0.0,
                    'deal_share_percent' => 0.0,
                    'amount_share_percent' => 0.0,
                ];
            }
            $marketingRows[$marketingKey]['payment_count']++;
            $marketingRows[$marketingKey]['amount'] += $amount;
            $marketingTotal['payment_count']++;
            $marketingTotal['amount'] += $amount;
            if ($dealId > 0) {
                $marketingDeals[$marketingKey][$dealId] = true;
                $marketingTotal['deal_ids'][$dealId] = true;
            }
        }

        foreach ($cashRows as $key => $row) {
            $cashRows[$key] = $this->ceoFinalizePaymentAggregate($row);
        }
        $totalDeposits = $this->ceoFinalizePaymentAggregate($totalDeposits);
        foreach ($paymentDealRangeSplit as $key => $row) {
            $paymentDealRangeSplit[$key] = $this->ceoFinalizePaymentAggregate($row);
        }
        $depositCohortRows = array_values($paymentDealRangeSplit);
        foreach ($marketingRows as $key => $row) {
            $marketingRows[$key]['deal_count'] = count($marketingDeals[$key] ?? []);
        }
        $marketingTotal['deal_count'] = count($marketingTotal['deal_ids'] ?? []);
        $this->applyRoundedPercentShares($marketingRows, 'payment_count', 'payment_share_percent', (float)($marketingTotal['payment_count'] ?? 0));
        $this->applyRoundedPercentShares($marketingRows, 'deal_count', 'deal_share_percent', (float)($marketingTotal['deal_count'] ?? 0));
        $this->applyRoundedPercentShares($marketingRows, 'amount', 'amount_share_percent', (float)($marketingTotal['amount'] ?? 0));
        usort($marketingRows, static function ($a, $b) {
            $cmp = ((float)($b['amount'] ?? 0)) <=> ((float)($a['amount'] ?? 0));
            return $cmp !== 0 ? $cmp : ((int)($b['deal_count'] ?? 0) <=> (int)($a['deal_count'] ?? 0));
        });
        $agentTotal['deal_count'] = count($agentTotal['deal_ids'] ?? []);
        $agentTotal['sales_deal_count'] = count($agentTotal['sales_deal_ids'] ?? []);
        foreach ($agentRows as $key => $row) {
            $agentRows[$key]['deal_count'] = count($agentDeals[$key] ?? []);
            $agentRows[$key]['sales_deal_count'] = count($agentSalesDeals[$key] ?? []);
            $this->ceoApplyLeadSourceCounts($agentRows[$key], $agentLeadSourceDeals[$key] ?? []);
        }
        foreach (['crm', 'personal', 'sudran'] as $sourceKey) {
            $agentTotal[$sourceKey . '_lead_count'] = count($agentTotal['lead_source_deals'][$sourceKey] ?? []);
        }
        $this->applyRoundedPercentShares($agentRows, 'sales_deal_count', 'sales_count_share_percent', (float)($agentTotal['sales_deal_count'] ?? 0));
        $this->applyRoundedPercentShares($agentRows, 'sales_amount', 'sales_amount_share_percent', (float)($agentTotal['sales_amount'] ?? 0));
        $this->applyRoundedPercentShares($agentRows, 'payment_count', 'payment_count_share_percent', (float)($agentTotal['payment_count'] ?? 0));
        $this->applyRoundedPercentShares($agentRows, 'payment_amount', 'payment_amount_share_percent', (float)($agentTotal['payment_amount'] ?? 0));
        usort($agentRows, static function ($a, $b) {
            $cmp = ((float)($b['sales_amount'] ?? 0)) <=> ((float)($a['sales_amount'] ?? 0));
            if ($cmp !== 0) {
                return $cmp;
            }
            $cmp = ((float)($b['payment_amount'] ?? 0)) <=> ((float)($a['payment_amount'] ?? 0));
            return $cmp !== 0 ? $cmp : strcmp((string)($a['owner_name'] ?? ''), (string)($b['owner_name'] ?? ''));
        });

        $productRows = $this->getCeoProductSalesRows($filters, $paymentRows);
        $outstanding = $this->getCeoOutstandingBalanceRows($filters);
        $chequeStatus = $this->getCeoChequeStateRows($filters);
        $dealStatus = $this->getCeoDealStatusRows($filters, $outstanding);

        $summary['total_deposits_amount'] = (float)$totalDeposits['amount'];
        $summary['total_deposits_deal_count'] = (int)$totalDeposits['deal_count'];
        $summary['total_deposits_receipt_count'] = (int)$totalDeposits['payment_count'];
        $summary['deposit_registered_in_range_amount'] = (float)($paymentDealRangeSplit['registered_range_deposit']['amount'] ?? 0);
        $summary['deposit_registered_in_range_deal_count'] = (int)($paymentDealRangeSplit['registered_range_deposit']['deal_count'] ?? 0);
        $summary['deposit_registered_in_range_receipt_count'] = (int)($paymentDealRangeSplit['registered_range_deposit']['payment_count'] ?? 0);
        $summary['deposit_outside_registered_first_payment_amount'] = (float)($paymentDealRangeSplit['outside_range_deposit']['amount'] ?? 0);
        $summary['deposit_outside_registered_first_payment_deal_count'] = (int)($paymentDealRangeSplit['outside_range_deposit']['deal_count'] ?? 0);
        $summary['deposit_outside_registered_first_payment_receipt_count'] = (int)($paymentDealRangeSplit['outside_range_deposit']['payment_count'] ?? 0);
        $summary['sales_deposit_amount'] = (float)$cashRows['sales_deposit']['amount'];
        $summary['sales_deposit_deal_count'] = (int)$cashRows['sales_deposit']['deal_count'];
        $summary['sales_deposit_receipt_count'] = (int)$cashRows['sales_deposit']['payment_count'];
        $summary['balance_deposit_amount'] = (float)$cashRows['balance_deposit']['amount'];
        $summary['balance_deposit_deal_count'] = (int)$cashRows['balance_deposit']['deal_count'];
        $summary['balance_deposit_receipt_count'] = (int)$cashRows['balance_deposit']['payment_count'];
        $summary['cheque_collected_amount'] = (float)$cashRows['cheque_collected']['amount'];
        $summary['cheque_collected_deal_count'] = (int)$cashRows['cheque_collected']['deal_count'];
        $summary['cheque_collected_count'] = (int)$cashRows['cheque_collected']['payment_count'];
        $summary['sudran_deposit_amount'] = (float)$cashRows['sudran_deposit']['amount'];
        $summary['sudran_deposit_deal_count'] = (int)$cashRows['sudran_deposit']['deal_count'];
        $summary['sudran_deposit_receipt_count'] = (int)$cashRows['sudran_deposit']['payment_count'];
        $summary['lost_deposit_amount'] = (float)$cashRows['lost_deposit']['amount'];
        $summary['lost_deposit_deal_count'] = (int)$cashRows['lost_deposit']['deal_count'];
        $summary['lost_deposit_receipt_count'] = (int)$cashRows['lost_deposit']['payment_count'];
        $summary['outstanding_balance_amount'] = (float)($outstanding['total']['amount'] ?? 0);
        $summary['outstanding_balance_deal_count'] = (int)($outstanding['total']['deal_count'] ?? 0);
        $summary['all_cheques_count'] = (int)($chequeStatus['total']['count'] ?? 0);
        $summary['all_cheques_amount'] = (float)($chequeStatus['total']['amount'] ?? 0);
        $summary['registered_deals_count'] = (int)($dealStatus['total_deals'] ?? 0);
        foreach (($dealStatus['cards'] ?? []) as $statusCard) {
            $statusKey = (string)($statusCard['key'] ?? '');
            if ($statusKey === 'registered_in_range_deals') {
                $summary['registered_in_range_deals_count'] = (int)($statusCard['count'] ?? 0);
            } elseif ($statusKey === 'first_payment_outside_registered_deals') {
                $summary['first_payment_outside_registered_deals_count'] = (int)($statusCard['count'] ?? 0);
            }
        }
        $summary['products_sold_count'] = array_sum(array_map(static function ($row) {
            return (int)($row['sold_count'] ?? 0);
        }, $productRows));

        $accessRows = $this->getCeoAccessGrantRows($filters);
        foreach ($accessRows as $row) {
            if ((string)($row['status'] ?? '') === 'done') {
                $summary['access_granted_count'] += (int)($row['count'] ?? 0);
            }
        }

        // Backward-compatible aliases for existing drilldowns/tests; the UI uses the new names.
        $summary['first_payment_amount'] = $summary['sales_deposit_amount'];
        $summary['first_payment_deal_count'] = $summary['sales_deposit_deal_count'];
        $summary['first_payment_receipt_count'] = $summary['sales_deposit_receipt_count'];
        $summary['balance_current_amount'] = $summary['balance_deposit_amount'];
        $summary['balance_current_deal_count'] = $summary['balance_deposit_deal_count'];
        $summary['balance_current_receipt_count'] = $summary['balance_deposit_receipt_count'];

        return [
            'summary' => $summary,
            'deposit_total' => $totalDeposits,
            'deposit_cohort' => $depositCohortRows,
            'cash_composition' => array_values($cashRows),
            'outstanding_balances' => $outstanding,
            'product_sales' => array_slice(array_values($productRows), 0, 50),
            'marketing_sources' => array_slice(array_values($marketingRows), 0, 100),
            'agent_sales' => array_slice(array_values($agentRows), 0, 100),
            'registered_deals_sources' => array_slice(array_values($marketingRows), 0, 100),
            'deal_status' => $dealStatus,
            'access_grants' => $accessRows,
            'cheques' => $chequeStatus,
        ];
    }

    private function emptyCeoPerformanceReport(): array
    {
        return [
            'summary' => $this->emptyCeoSummary(),
            'deposit_total' => $this->ceoFinalizePaymentAggregate($this->ceoInitAggregateRow('total_deposits', 'کل واریزی‌ها')),
            'deposit_cohort' => [],
            'cash_composition' => [],
            'outstanding_balances' => ['total' => ['amount' => 0, 'deal_count' => 0], 'months' => []],
            'product_sales' => [],
            'marketing_sources' => [],
            'agent_sales' => [],
            'registered_deals_sources' => [],
            'deal_status' => ['total_deals' => 0, 'cards' => [], 'lead_mix' => []],
            'access_grants' => [],
            'cheques' => ['total' => ['count' => 0, 'amount' => 0], 'cards' => [], 'by_status' => [], 'by_collection_status' => []],
        ];
    }

    private function emptyCeoSummary(): array
    {
        return [
            'registered_deals_count' => 0,
            'registered_in_range_deals_count' => 0,
            'first_payment_outside_registered_deals_count' => 0,
            'access_granted_count' => 0,
            'total_deposits_amount' => 0,
            'total_deposits_deal_count' => 0,
            'total_deposits_receipt_count' => 0,
            'deposit_registered_in_range_amount' => 0,
            'deposit_registered_in_range_deal_count' => 0,
            'deposit_registered_in_range_receipt_count' => 0,
            'deposit_outside_registered_first_payment_amount' => 0,
            'deposit_outside_registered_first_payment_deal_count' => 0,
            'deposit_outside_registered_first_payment_receipt_count' => 0,
            'sales_deposit_amount' => 0,
            'sales_deposit_deal_count' => 0,
            'sales_deposit_receipt_count' => 0,
            'balance_deposit_amount' => 0,
            'balance_deposit_deal_count' => 0,
            'balance_deposit_receipt_count' => 0,
            'cheque_collected_amount' => 0,
            'cheque_collected_deal_count' => 0,
            'cheque_collected_count' => 0,
            'sudran_deposit_amount' => 0,
            'sudran_deposit_deal_count' => 0,
            'sudran_deposit_receipt_count' => 0,
            'lost_deposit_amount' => 0,
            'lost_deposit_deal_count' => 0,
            'lost_deposit_receipt_count' => 0,
            'outstanding_balance_amount' => 0,
            'outstanding_balance_deal_count' => 0,
            'all_cheques_count' => 0,
            'all_cheques_amount' => 0,
            'products_sold_count' => 0,
            'registered_payments_count' => 0,
            'first_payment_amount' => 0,
            'first_payment_deal_count' => 0,
            'first_payment_receipt_count' => 0,
            'balance_current_amount' => 0,
            'balance_current_deal_count' => 0,
            'balance_current_receipt_count' => 0,
            'balance_previous_amount' => 0,
            'balance_previous_deal_count' => 0,
            'balance_previous_receipt_count' => 0,
        ];
    }

    private function ceoInitAggregateRow(string $key, string $label): array
    {
        return [
            'key' => $key,
            'label' => $label,
            'amount' => 0.0,
            'payment_count' => 0,
            'deal_count' => 0,
            'deal_ids' => [],
        ];
    }

    private function ceoSourcePaymentAmountFields(): array
    {
        return [
            'crm_sales_amount',
            'crm_balance_amount',
            'crm_cheque_amount',
            'crm_lost_amount',
            'personal_sales_amount',
            'personal_balance_amount',
            'personal_cheque_amount',
            'personal_lost_amount',
            'sudran_amount',
        ];
    }

    private function ceoInitSourcePaymentAmounts(): array
    {
        $amounts = [];
        foreach ($this->ceoSourcePaymentAmountFields() as $field) {
            $amounts[$field] = 0.0;
        }
        return $amounts;
    }

    private function ceoInitLeadSourceDealBuckets(): array
    {
        return [
            'crm' => [],
            'personal' => [],
            'sudran' => [],
        ];
    }

    private function ceoAddSourcePaymentAmount(array &$target, string $leadSource, string $category, float $amount): void
    {
        $field = '';
        if ($leadSource === 'sudran') {
            $field = 'sudran_amount';
        } elseif ($leadSource === 'crm' || $leadSource === 'personal') {
            if ($category === 'sales_deposit') {
                $field = $leadSource . '_sales_amount';
            } elseif ($category === 'balance_deposit') {
                $field = $leadSource . '_balance_amount';
            } elseif ($category === 'cheque_collected') {
                $field = $leadSource . '_cheque_amount';
            } elseif ($category === 'lost_deposit') {
                $field = $leadSource . '_lost_amount';
            }
        }

        if ($field !== '') {
            if (!array_key_exists($field, $target)) {
                $target[$field] = 0.0;
            }
            $target[$field] += $amount;
        }
    }

    private function ceoMarkLeadSourceDeal(array &$buckets, string $leadSource, int $dealId): void
    {
        if ($dealId <= 0) {
            return;
        }
        foreach (['crm', 'personal', 'sudran'] as $sourceKey) {
            if (!isset($buckets[$sourceKey]) || !is_array($buckets[$sourceKey])) {
                $buckets[$sourceKey] = [];
            }
        }

        if ($leadSource === 'sudran') {
            unset($buckets['crm'][$dealId], $buckets['personal'][$dealId]);
            $buckets['sudran'][$dealId] = true;
            return;
        }

        if (($leadSource === 'crm' || $leadSource === 'personal') && empty($buckets['sudran'][$dealId])) {
            $buckets[$leadSource][$dealId] = true;
        }
    }

    private function ceoApplyLeadSourceCounts(array &$row, array $buckets): void
    {
        $denominator = (int)($row['deal_count'] ?? 0);
        $largestSource = null;
        $largestCount = -1;
        $roundedTotal = 0.0;

        foreach (['crm', 'personal', 'sudran'] as $sourceKey) {
            $countField = $sourceKey . '_lead_count';
            $shareField = $sourceKey . '_lead_share_percent';
            $count = count($buckets[$sourceKey] ?? []);
            $row[$countField] = $count;
            $row[$shareField] = $denominator > 0 ? round(($count / $denominator) * 100, 2) : 0.0;
            $roundedTotal += (float)$row[$shareField];
            if ($count > $largestCount) {
                $largestCount = $count;
                $largestSource = $sourceKey;
            }
        }

        $adjustment = round(100.0 - $roundedTotal, 2);
        if ($denominator > 0 && $largestSource !== null && abs($adjustment) >= 0.01) {
            $shareField = $largestSource . '_lead_share_percent';
            $row[$shareField] = round((float)$row[$shareField] + $adjustment, 2);
        }
    }

    private function ceoPaymentSplitTotalAmount(array $row): float
    {
        $total = 0.0;
        foreach ($this->ceoSourcePaymentAmountFields() as $field) {
            $total += (float)($row[$field] ?? 0);
        }
        return $total;
    }

    private function ceoAddPaymentToAggregate(array &$row, array $payment): void
    {
        $row['amount'] = (float)($row['amount'] ?? 0) + (float)($payment['net_amount'] ?? 0);
        $row['payment_count'] = (int)($row['payment_count'] ?? 0) + 1;
        $dealId = (int)($payment['deal_id'] ?? 0);
        if ($dealId > 0) {
            $row['deal_ids'][$dealId] = true;
        }
    }

    private function ceoFinalizePaymentAggregate(array $row): array
    {
        $row['deal_count'] = count($row['deal_ids'] ?? []);
        unset($row['deal_ids']);
        return $row;
    }

    private function ceoDateInFilterRange(string $date, array $filters): bool
    {
        $date = trim($date);
        if ($date === '') {
            return false;
        }
        [$dateFrom, $dateTo] = $this->dateRangeFromFilters($filters);
        return $date >= $dateFrom && $date <= $dateTo;
    }

    private function canonicalCeoMarketingLabel(string $kind, string $value): string
    {
        $raw = trim($value);
        if ($raw === '') {
            if ($kind === 'source') {
                return 'نامشخص';
            }
            return $kind === 'topic' ? 'بدون موضوع' : 'بدون جزئیات';
        }

        $key = strtolower($raw);
        $maps = [
            'source' => [
                'yes' => 'بله',
                'eitaa' => 'ایتا',
                'incoming_call' => 'تماس ورودی',
                'instagram' => 'اینستاگرام',
                'referral' => 'معرف',
                'old_customer' => 'مشتری قدیمی',
                'other' => 'سایر',
            ],
            'detail' => [
                'general' => 'عمومی',
                'generic' => 'عمومی',
                'referral_customer' => 'مشتری قدیم',
                'referral_internal' => 'همکاران داخلی',
                'referral_external' => 'همکاران خارج از سازمان',
                'referral_teacher' => 'استاد مطلبی',
            ],
            'topic' => [
                'vip_channel' => 'کانال VIP',
                'detailing_training' => 'آموزش دیتیلینگ',
                'car_intelligence_test' => 'تست هوش خودرو',
                'inspection_test' => 'تست کارشناسی',
                'other' => 'سایر',
            ],
        ];

        return $maps[$kind][$key] ?? $raw;
    }

    private function applyRoundedPercentShares(array &$rows, string $numeratorKey, string $targetKey, float $denominator): void
    {
        if ($denominator <= 0 || empty($rows)) {
            foreach ($rows as $key => $row) {
                $rows[$key][$targetKey] = 0.0;
            }
            return;
        }

        $roundedTotal = 0.0;
        $largestKey = null;
        $largestValue = -1.0;
        foreach ($rows as $key => $row) {
            $value = max(0.0, (float)($row[$numeratorKey] ?? 0));
            $share = round(($value / $denominator) * 100, 2);
            $rows[$key][$targetKey] = $share;
            $roundedTotal += $share;
            if ($value > $largestValue) {
                $largestValue = $value;
                $largestKey = $key;
            }
        }

        $adjustment = round(100.0 - $roundedTotal, 2);
        if ($largestKey !== null && abs($adjustment) >= 0.01) {
            $rows[$largestKey][$targetKey] = round((float)$rows[$largestKey][$targetKey] + $adjustment, 2);
        }
    }

    private function ceoJalaliMonthLabel(string $gregorianDate): array
    {
        $monthNames = [
            1 => 'فروردین',
            2 => 'اردیبهشت',
            3 => 'خرداد',
            4 => 'تیر',
            5 => 'مرداد',
            6 => 'شهریور',
            7 => 'مهر',
            8 => 'آبان',
            9 => 'آذر',
            10 => 'دی',
            11 => 'بهمن',
            12 => 'اسفند',
        ];
        try {
            [$jy, $jm] = DateNormalizer::gregorianToJalaliParts($gregorianDate);
        } catch (\Throwable $e) {
            return ['key' => 'unknown', 'label' => 'نامشخص', 'sort_key' => '0000-00'];
        }
        $key = sprintf('%04d-%02d', $jy, $jm);
        return [
            'key' => $key,
            'label' => ($monthNames[$jm] ?? (string)$jm) . ' ' . $jy,
            'sort_key' => $key,
        ];
    }

    private function getCeoProductSalesRows(array $filters, array $paymentRows): array
    {
        $salesByDeal = [];
        $productCohortDeals = [];
        $paymentsByDeal = [];
        $fallbackProduct = [];
        foreach ($paymentRows as $row) {
            $category = $this->classifyCeoPaymentRow($row, $filters);
            if ($category === '') {
                continue;
            }
            $dealId = (int)($row['deal_id'] ?? 0);
            if ($dealId <= 0) {
                continue;
            }
            $paymentsByDeal[$dealId][] = array_merge($row, ['_ceo_category' => $category]);
            if ($category === 'sales_deposit') {
                $salesByDeal[$dealId] = ($salesByDeal[$dealId] ?? 0.0) + (float)($row['net_amount'] ?? 0);
                $productCohortDeals[$dealId] = true;
            } elseif ($category === 'sudran_deposit') {
                $productCohortDeals[$dealId] = true;
            }
            if (!isset($fallbackProduct[$dealId])) {
                $fallbackProduct[$dealId] = trim((string)($row['product_name'] ?? '')) ?: 'نامشخص';
            }
        }
        if (empty($productCohortDeals)) {
            return [];
        }

        $dealIds = array_keys($productCohortDeals);
        $placeholders = implode(',', array_fill(0, count($dealIds), '?'));
        $lineRows = $this->runSelectAll("
            SELECT
                dp.deal_id,
                COALESCE(NULLIF(TRIM(dp.service_title_snapshot), ''), NULLIF(TRIM(dp.product_name_snapshot), ''), NULLIF(TRIM(p.name), ''), 'نامشخص') as product_name,
                GREATEST(COALESCE(dp.quantity, 1), 1) as quantity,
                COALESCE(NULLIF(dp.total, 0), NULLIF(dp.price, 0) * GREATEST(COALESCE(dp.quantity, 1), 1), NULLIF(dp.unit_price_snapshot, 0) * GREATEST(COALESCE(dp.quantity, 1), 1), 0) as line_total
            FROM deal_products dp
            LEFT JOIN products p ON p.id = dp.product_id
            WHERE dp.deal_id IN ({$placeholders})
            ORDER BY dp.deal_id ASC, dp.id ASC
        ", $dealIds);

        $byDeal = [];
        foreach ($lineRows as $line) {
            $dealId = (int)($line['deal_id'] ?? 0);
            if ($dealId <= 0) {
                continue;
            }
            $byDeal[$dealId][] = $line;
        }

        $products = [];
        $productDeals = [];
        $productLeadSourceDeals = [];
        foreach ($productCohortDeals as $dealId => $_included) {
            $lines = $byDeal[$dealId] ?? [];
            if (empty($lines)) {
                $lines = [[
                    'deal_id' => $dealId,
                    'product_name' => $fallbackProduct[$dealId] ?? 'نامشخص',
                    'quantity' => 1,
                    'line_total' => 0,
                ]];
            }
            $totalLine = 0.0;
            foreach ($lines as $line) {
                $totalLine += max(0.0, (float)($line['line_total'] ?? 0));
            }
            $lineCount = max(1, count($lines));
            foreach ($lines as $line) {
                $productName = trim((string)($line['product_name'] ?? '')) ?: 'نامشخص';
                $qty = max(1, (int)($line['quantity'] ?? 1));
                $lineTotal = max(0.0, (float)($line['line_total'] ?? 0));
                if (!isset($products[$productName])) {
                    $products[$productName] = array_merge([
                        'product_name' => $productName,
                        'sold_count' => 0,
                        'deal_count' => 0,
                        'amount' => 0.0,
                        'payment_amount' => 0.0,
                        'sold_share_percent' => 0.0,
                        'amount_share_percent' => 0.0,
                        'payment_amount_share_percent' => 0.0,
                        'crm_lead_count' => 0,
                        'crm_lead_share_percent' => 0.0,
                        'personal_lead_count' => 0,
                        'personal_lead_share_percent' => 0.0,
                        'sudran_lead_count' => 0,
                        'sudran_lead_share_percent' => 0.0,
                    ], $this->ceoInitSourcePaymentAmounts());
                    $productLeadSourceDeals[$productName] = $this->ceoInitLeadSourceDealBuckets();
                }
                $products[$productName]['sold_count'] += $qty;
                $productDeals[$productName][$dealId] = true;
            }

            foreach (($paymentsByDeal[$dealId] ?? []) as $paymentRow) {
                $category = (string)($paymentRow['_ceo_category'] ?? '');
                if ($category === '') {
                    continue;
                }
                $amount = (float)($paymentRow['net_amount'] ?? 0);
                if ($amount <= 0) {
                    continue;
                }
                $leadSource = $this->ceoLeadSourceBucket($paymentRow, $category);
                foreach ($lines as $line) {
                    $productName = trim((string)($line['product_name'] ?? '')) ?: 'نامشخص';
                    $lineTotal = max(0.0, (float)($line['line_total'] ?? 0));
                    $allocated = $totalLine > 0 ? ($amount * ($lineTotal / $totalLine)) : ($amount / $lineCount);
                    if (!isset($products[$productName])) {
                        continue;
                    }
                    $products[$productName]['payment_amount'] += $allocated;
                    if ($category === 'sales_deposit') {
                        $products[$productName]['amount'] += $allocated;
                    }
                    $this->ceoAddSourcePaymentAmount($products[$productName], $leadSource, $category, $allocated);
                    $this->ceoMarkLeadSourceDeal($productLeadSourceDeals[$productName], $leadSource, (int)$dealId);
                }
            }
        }

        $totalSold = array_sum(array_map(static function ($row) {
            return (int)($row['sold_count'] ?? 0);
        }, $products));
        $totalProductAmount = array_sum(array_map(static function ($row) {
            return (float)($row['amount'] ?? 0);
        }, $products));
        $totalProductPaymentAmount = array_sum(array_map(static function ($row) {
            return (float)($row['payment_amount'] ?? 0);
        }, $products));
        foreach ($products as $productName => $row) {
            $products[$productName]['deal_count'] = count($productDeals[$productName] ?? []);
            $this->ceoApplyLeadSourceCounts($products[$productName], $productLeadSourceDeals[$productName] ?? []);
        }
        $this->applyRoundedPercentShares($products, 'sold_count', 'sold_share_percent', (float)$totalSold);
        $this->applyRoundedPercentShares($products, 'amount', 'amount_share_percent', (float)$totalProductAmount);
        $this->applyRoundedPercentShares($products, 'payment_amount', 'payment_amount_share_percent', (float)$totalProductPaymentAmount);
        usort($products, static function ($a, $b) {
            $cmp = ((float)($b['amount'] ?? 0)) <=> ((float)($a['amount'] ?? 0));
            return $cmp !== 0 ? $cmp : strcmp((string)($a['product_name'] ?? ''), (string)($b['product_name'] ?? ''));
        });
        return array_values($products);
    }

    private function getCeoAllTimeNormalizedCustomerPaymentFactsByDeal(array $filters, array $dealIds = []): array
    {
        $dealIds = array_values(array_unique(array_filter(array_map('intval', $dealIds))));
        $wanted = !empty($dealIds) ? array_fill_keys($dealIds, true) : [];
        $facts = [];
        foreach ($this->getCeoLeanPaymentRowsBetween($filters, '1900-01-01', '2999-12-31', $dealIds) as $row) {
            $dealId = (int)($row['deal_id'] ?? 0);
            $amount = (float)($row['net_amount'] ?? 0);
            if ($dealId <= 0 || $amount <= 0) {
                continue;
            }
            if (!empty($wanted) && !isset($wanted[$dealId])) {
                continue;
            }
            if ((int)($row['is_sudran'] ?? 0) === 1) {
                continue;
            }
            if ((int)($row['is_cheque'] ?? 0) === 1 && (string)($row['cheque_collection_status'] ?? '') !== 'collected') {
                continue;
            }
            $paymentDate = trim((string)($row['payment_window_date'] ?? ''));
            if (!isset($facts[$dealId])) {
                $facts[$dealId] = [
                    'total_paid' => 0.0,
                    'first_payment_date' => '',
                    'payment_count' => 0,
                ];
            }
            $facts[$dealId]['total_paid'] += $amount;
            $facts[$dealId]['payment_count']++;
            if ($paymentDate !== '' && ($facts[$dealId]['first_payment_date'] === '' || $paymentDate < $facts[$dealId]['first_payment_date'])) {
                $facts[$dealId]['first_payment_date'] = $paymentDate;
            }
        }
        return $facts;
    }

    private function getCeoOutstandingBalanceDealRows(array $filters): array
    {
        $dealWhere = $this->buildWhereClauseForDealsNoDate($filters, 'd');
        $dealParams = $this->buildParamsForDealsNoDate($filters);
        $paymentFacts = $this->getCeoAllTimeNormalizedCustomerPaymentFactsByDeal($filters);
        $dealRows = $this->runSelectAll("
            SELECT
                d.id as deal_id,
                COALESCE(d.payable_amount, 0) as payable_amount
            FROM deals d
            WHERE {$dealWhere}
              AND COALESCE(d.status, 'Pending') <> 'Lost'
              AND COALESCE(d.payable_amount, 0) > 0
        ", $dealParams);

        $rows = [];
        foreach ($dealRows as $dealRow) {
            $dealId = (int)($dealRow['deal_id'] ?? 0);
            if ($dealId <= 0 || !isset($paymentFacts[$dealId])) {
                continue;
            }
            $payable = (float)($dealRow['payable_amount'] ?? 0);
            $totalPaid = (float)($paymentFacts[$dealId]['total_paid'] ?? 0);
            $outstanding = max($payable - $totalPaid, 0);
            if ($outstanding <= 0) {
                continue;
            }
            $rows[] = [
                'deal_id' => $dealId,
                'payable_amount' => $payable,
                'first_payment_date' => (string)($paymentFacts[$dealId]['first_payment_date'] ?? ''),
                'total_paid' => $totalPaid,
                'outstanding_amount' => $outstanding,
            ];
        }

        usort($rows, static function ($a, $b) {
            $dateCmp = strcmp((string)($b['first_payment_date'] ?? ''), (string)($a['first_payment_date'] ?? ''));
            if ($dateCmp !== 0) {
                return $dateCmp;
            }
            return (int)($b['deal_id'] ?? 0) <=> (int)($a['deal_id'] ?? 0);
        });

        return $rows;
    }

    private function getCeoOutstandingBalanceRows(array $filters): array
    {
        $rows = $this->getCeoOutstandingBalanceDealRows($filters);
        $total = ['amount' => 0.0, 'deal_count' => 0];
        $months = [];
        $monthDeals = [];
        $visibleMonthStartKey = $this->ceoOutstandingVisibleMonthStartKey($filters);
        foreach ($rows as $row) {
            $amount = (float)($row['outstanding_amount'] ?? 0);
            $dealId = (int)($row['deal_id'] ?? 0);
            if ($amount <= 0 || $dealId <= 0) {
                continue;
            }
            $month = $this->ceoJalaliMonthLabel((string)($row['first_payment_date'] ?? ''));
            $key = $month['key'];
            if ($visibleMonthStartKey !== '' && (!preg_match('/^\d{4}-\d{2}$/', $key) || $key < $visibleMonthStartKey)) {
                continue;
            }
            $total['amount'] += $amount;
            $total['deal_count']++;
            if (!isset($months[$key])) {
                $months[$key] = [
                    'key' => $key,
                    'label' => $month['label'],
                    'sort_key' => $month['sort_key'],
                    'amount' => 0.0,
                    'deal_count' => 0,
                ];
            }
            $months[$key]['amount'] += $amount;
            $monthDeals[$key][$dealId] = true;
        }
        foreach ($months as $key => $row) {
            $months[$key]['deal_count'] = count($monthDeals[$key] ?? []);
        }
        usort($months, static function ($a, $b) {
            return strcmp((string)($b['sort_key'] ?? ''), (string)($a['sort_key'] ?? ''));
        });
        return ['total' => $total, 'months' => array_values($months)];
    }

    private function ceoOutstandingVisibleMonthStartKey(array $filters): string
    {
        $reference = trim((string)($filters['date_to'] ?? ''));
        if ($reference === '') {
            $reference = (new \DateTimeImmutable('now', new \DateTimeZone('Asia/Tehran')))->format('Y-m-d');
        }

        try {
            [$jy, $jm] = DateNormalizer::gregorianToJalaliParts($reference);
        } catch (\Throwable $e) {
            return '';
        }

        $startYear = $jm >= 12 ? $jy : $jy - 1;
        return sprintf('%04d-%02d', $startYear, 12);
    }

    private function getCeoChequeStateRows(array $filters): array
    {
        $dealWhere = $this->buildWhereClauseForDealsNoDate($filters, 'd');
        $dealParams = $this->buildParamsForDealsNoDate($filters);
        $isCheque = $this->ceoPaymentIsChequeExpr('pr');
        $valid = $this->ceoPaymentValidWhere('pr');
        $netAmount = $this->ceoPaymentNetAmountExpr('pr');
        $today = (new \DateTimeImmutable('now', new \DateTimeZone('Asia/Tehran')))->format('Y-m-d');
        $currentJalaliRange = DateNormalizer::currentJalaliMonthRange($today);
        $currentJalaliMonthStart = (string)($currentJalaliRange['date_from'] ?? $today);
        $rows = $this->runSelectAll("
            SELECT
                pr.id as payment_record_id,
                pr.deal_id,
                pr.deal_didar_id,
                pr.payment_uuid,
                pr.source_type,
                {$netAmount} as net_amount,
                pr.cheque_due_date,
                pr.cheque_collection_date,
                COALESCE(pr.cheque_status, 'pending') as cheque_status,
                COALESCE(pr.cheque_collection_status, 'pending') as cheque_collection_status,
                COALESCE(d.title, '') as deal_title,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name
            FROM payment_records pr
            INNER JOIN deals d ON d.id = pr.deal_id
            LEFT JOIN users u ON u.didar_user_id = d.owner_didar_id
            WHERE {$valid}
              AND {$netAmount} > 0
              AND {$isCheque}
              AND {$dealWhere}
            ORDER BY COALESCE(pr.cheque_due_date, pr.cheque_collection_date, pr.created_at) ASC, pr.id ASC
        ", $dealParams);

        $total = ['key' => 'all_cheques_count', 'label' => 'کل چک‌ها', 'count' => 0, 'deal_count' => 0, 'amount' => 0.0];
        $cards = [
            'collected_cheques' => ['key' => 'collected_cheques', 'label' => 'چک‌های پاس‌شده', 'count' => 0, 'deal_count' => 0, 'amount' => 0.0, 'deal_ids' => []],
            'returned_cheques' => ['key' => 'returned_cheques', 'label' => 'چک‌های برگشت‌خورده', 'count' => 0, 'deal_count' => 0, 'amount' => 0.0, 'deal_ids' => []],
            'due_unresolved_cheques' => ['key' => 'due_unresolved_cheques', 'label' => 'سررسید رسیده، تعیین‌تکلیف نشده', 'count' => 0, 'deal_count' => 0, 'amount' => 0.0, 'deal_ids' => []],
            'undated_pending_cheques' => ['key' => 'undated_pending_cheques', 'label' => 'بدون تاریخ سررسید', 'count' => 0, 'deal_count' => 0, 'amount' => 0.0, 'deal_ids' => []],
        ];
        $monthDeals = [];
        $allDeals = [];
        $byStatus = [];
        $byCollection = [];

        foreach ($rows as $row) {
            $amount = (float)($row['net_amount'] ?? 0);
            $dealId = (int)($row['deal_id'] ?? 0);
            $collection = (string)($row['cheque_collection_status'] ?? 'pending');
            $status = (string)($row['cheque_status'] ?? 'pending');
            $dueDate = trim((string)($row['cheque_due_date'] ?? ''));
            $total['count']++;
            $total['amount'] += $amount;
            if ($dealId > 0) {
                $allDeals[$dealId] = true;
            }

            if (!isset($byStatus[$status])) {
                $byStatus[$status] = ['status' => $status, 'count' => 0, 'deal_count' => 0, 'amount' => 0.0, 'deal_ids' => []];
            }
            $byStatus[$status]['count']++;
            $byStatus[$status]['amount'] += $amount;
            if ($dealId > 0) {
                $byStatus[$status]['deal_ids'][$dealId] = true;
            }

            if (!isset($byCollection[$collection])) {
                $byCollection[$collection] = ['status' => $collection, 'count' => 0, 'deal_count' => 0, 'amount' => 0.0, 'deal_ids' => []];
            }
            $byCollection[$collection]['count']++;
            $byCollection[$collection]['amount'] += $amount;
            if ($dealId > 0) {
                $byCollection[$collection]['deal_ids'][$dealId] = true;
            }

            $isReturned = in_array($collection, ['returned', 'bounced'], true) || in_array($status, ['returned', 'rejected'], true);
            $isClosed = $collection === 'collected' || $isReturned;
            if ($collection === 'collected') {
                $cards['collected_cheques']['count']++;
                $cards['collected_cheques']['amount'] += $amount;
                if ($dealId > 0) {
                    $cards['collected_cheques']['deal_ids'][$dealId] = true;
                }
            }
            if ($isReturned) {
                $cards['returned_cheques']['count']++;
                $cards['returned_cheques']['amount'] += $amount;
                if ($dealId > 0) {
                    $cards['returned_cheques']['deal_ids'][$dealId] = true;
                }
            }
            if (!$isClosed && $dueDate !== '' && $dueDate <= $today) {
                $cards['due_unresolved_cheques']['count']++;
                $cards['due_unresolved_cheques']['amount'] += $amount;
                if ($dealId > 0) {
                    $cards['due_unresolved_cheques']['deal_ids'][$dealId] = true;
                }
            }
            if (!$isClosed && $dueDate === '') {
                $cards['undated_pending_cheques']['count']++;
                $cards['undated_pending_cheques']['amount'] += $amount;
                if ($dealId > 0) {
                    $cards['undated_pending_cheques']['deal_ids'][$dealId] = true;
                }
            }
            if (!$isClosed && $dueDate !== '' && $dueDate > $today && $dueDate >= $currentJalaliMonthStart) {
                $month = $this->ceoJalaliMonthLabel($dueDate);
                $key = 'cheque_due_month_' . $month['key'];
                if (!isset($cards[$key])) {
                    $cards[$key] = [
                        'key' => $key,
                        'month_key' => $month['key'],
                        'label' => $month['label'],
                        'sort_key' => $month['sort_key'],
                        'count' => 0,
                        'deal_count' => 0,
                        'amount' => 0.0,
                        'deal_ids' => [],
                    ];
                }
                $cards[$key]['count']++;
                $cards[$key]['amount'] += $amount;
                if ($dealId > 0) {
                    $cards[$key]['deal_ids'][$dealId] = true;
                    $monthDeals[$key][$dealId] = true;
                }
            }
        }

        $total['deal_count'] = count($allDeals);
        foreach ($cards as $key => $row) {
            $cards[$key]['deal_count'] = count($row['deal_ids'] ?? []);
            unset($cards[$key]['deal_ids']);
        }
        foreach ($byStatus as $key => $row) {
            $byStatus[$key]['deal_count'] = count($row['deal_ids'] ?? []);
            unset($byStatus[$key]['deal_ids']);
        }
        foreach ($byCollection as $key => $row) {
            $byCollection[$key]['deal_count'] = count($row['deal_ids'] ?? []);
            unset($byCollection[$key]['deal_ids']);
        }

        $cards = array_values(array_filter($cards, static function ($row) {
            return (int)($row['count'] ?? 0) > 0 || (float)($row['amount'] ?? 0) > 0;
        }));
        usort($cards, static function ($a, $b) {
            $priority = static function ($row): string {
                $key = (string)($row['key'] ?? '');
                if ($key === 'collected_cheques') {
                    return '0000-01';
                }
                if ($key === 'returned_cheques') {
                    return '0000-02';
                }
                if ($key === 'due_unresolved_cheques') {
                    return '0000-03';
                }
                if ($key === 'undated_pending_cheques') {
                    return '9999-99';
                }
                return (string)($row['sort_key'] ?? '9999-98');
            };
            $ak = $priority($a);
            $bk = $priority($b);
            if (strpos((string)($a['key'] ?? ''), 'cheque_due_month_') === 0 && strpos((string)($b['key'] ?? ''), 'cheque_due_month_') === 0) {
                return strcmp($ak, $bk);
            }
            if (strpos((string)($a['key'] ?? ''), 'cheque_due_month_') === 0) {
                return 1;
            }
            if (strpos((string)($b['key'] ?? ''), 'cheque_due_month_') === 0) {
                return -1;
            }
            return 0;
        });

        return [
            'total' => $total,
            'cards' => $cards,
            'by_status' => array_values($byStatus),
            'by_collection_status' => array_values($byCollection),
        ];
    }

    private function getCeoDealStatusRows(array $filters, array $outstandingBalances = []): array
    {
        [$dateFrom, $dateTo] = $this->dateRangeFromFilters($filters);
        $paymentOverlayByDeal = $this->buildCeoPaymentOverlayByDeal($filters);
        $whereDeals = $this->buildWhereClauseForDealsNoDate($filters, 'd');
        $params = $this->buildParamsForDealsNoDate($filters);
        $paymentDate = $this->ceoPaymentDateExpr('pr');
        $netAmount = $this->ceoPaymentNetAmountExpr('pr');
        $isCheque = $this->ceoPaymentIsChequeExpr('pr');
        $isSudran = $this->ceoPaymentIsSudranExpr('pr');
        $valid = $this->ceoPaymentValidWhere('pr');
        $paymentOverlayByDeal = $this->buildCeoPaymentOverlayByDeal($filters);
        $noAnswerExpr = "(LOWER(CONCAT_WS(' ', COALESCE(a.activity_type_title, ''), COALESCE(a.title, ''), COALESCE(a.note, ''), COALESCE(a.result_note, ''), COALESCE(a.call_type, ''))) LIKE '%no answer%'
            OR CONCAT_WS(' ', COALESCE(a.activity_type_title, ''), COALESCE(a.title, ''), COALESCE(a.note, ''), COALESCE(a.result_note, ''), COALESCE(a.call_type, '')) LIKE '%عدم پاسخ%'
            OR CONCAT_WS(' ', COALESCE(a.activity_type_title, ''), COALESCE(a.title, ''), COALESCE(a.note, ''), COALESCE(a.result_note, ''), COALESCE(a.call_type, '')) LIKE '%بی پاسخ%')";

        $rows = $this->runSelectAll("
            SELECT
                d.id as deal_id,
                d.status,
                DATE(COALESCE(d.register_time, d.created_at)) as deal_date,
                COALESCE(d.payable_amount, 0) as payable_amount,
                COALESCE(pay.total_paid, 0) as total_paid,
                COALESCE(pay.payment_in_range_amount, 0) as payment_in_range_amount,
                COALESCE(pay.has_sudran_in_range, 0) as has_sudran_in_range,
                pay.first_payment_date,
                COALESCE(act.activity_count, 0) as activity_count,
                COALESCE(act.no_answer_count, 0) as no_answer_count,
                COALESCE(act.successful_activity_count, 0) as successful_activity_count,
                DATE(COALESCE(p.register_time, p.created_at)) as lead_register_date,
                COALESCE(p.sale_status, '') as lead_sale_status,
                COALESCE(p.has_previous_purchase, 0) as has_previous_purchase,
                firstd.first_deal_date,
                COALESCE(cu.role, '') as creator_role
            FROM deals d
            LEFT JOIN persons p ON p.didar_contact_id = d.contact_didar_id
            LEFT JOIN users cu ON cu.didar_user_id = d.created_by_didar_id
            LEFT JOIN (
                SELECT
                    pr.deal_id,
                    SUM(CASE WHEN {$isSudran} = 0 AND ({$isCheque} = 0 OR pr.cheque_collection_status = 'collected') THEN {$netAmount} ELSE 0 END) as total_paid,
                    SUM(CASE WHEN {$paymentDate} BETWEEN ? AND ? AND ({$isCheque} = 0 OR pr.cheque_collection_status = 'collected') THEN {$netAmount} ELSE 0 END) as payment_in_range_amount,
                    MAX(CASE WHEN {$isSudran} = 1 AND {$paymentDate} BETWEEN ? AND ? THEN 1 ELSE 0 END) as has_sudran_in_range,
                    MIN(CASE WHEN {$isSudran} = 0 AND ({$isCheque} = 0 OR pr.cheque_collection_status = 'collected') THEN {$paymentDate} ELSE NULL END) as first_payment_date
                FROM payment_records pr
                WHERE {$valid}
                  AND {$netAmount} > 0
                  AND {$paymentDate} IS NOT NULL
                GROUP BY pr.deal_id
            ) pay ON pay.deal_id = d.id
            LEFT JOIN (
                SELECT
                    a.deal_didar_id,
                    COUNT(*) as activity_count,
                    SUM(CASE WHEN {$noAnswerExpr} THEN 1 ELSE 0 END) as no_answer_count,
                    SUM(CASE WHEN {$noAnswerExpr} THEN 0 ELSE 1 END) as successful_activity_count
                FROM activities a
                GROUP BY a.deal_didar_id
            ) act ON act.deal_didar_id = d.didar_deal_id
            LEFT JOIN (
                SELECT contact_didar_id, MIN(DATE(COALESCE(register_time, created_at))) as first_deal_date
                FROM deals
                GROUP BY contact_didar_id
            ) firstd ON firstd.contact_didar_id = d.contact_didar_id
            WHERE {$whereDeals}
            ORDER BY DATE(COALESCE(d.register_time, d.created_at)) DESC, d.id DESC
        ", array_merge([$dateFrom, $dateTo, $dateFrom, $dateTo], $params));

        $cards = [
            'total_registered_deals' => $this->ceoInitDealStatusCard('total_registered_deals', 'کل معاملات ثبت‌شده'),
            'no_activity_deals' => $this->ceoInitDealStatusCard('no_activity_deals', 'بدون فعالیت'),
            'no_answer_only_deals' => $this->ceoInitDealStatusCard('no_answer_only_deals', 'فقط عدم پاسخ'),
            'follow_up_deals' => $this->ceoInitDealStatusCard('follow_up_deals', 'در وضعیت پیگیری'),
            'lost_deals' => $this->ceoInitDealStatusCard('lost_deals', 'معاملات ناموفق'),
            'won_deals' => $this->ceoInitDealStatusCard('won_deals', 'معاملات موفق'),
            'won_outstanding_deals' => $this->ceoInitDealStatusCard('won_outstanding_deals', 'موفق دارای مانده حساب'),
        ];
        $previousSuccess = [
            'total' => $this->ceoInitDealStatusCard('first_payment_outside_registered_deals', 'ثبت شده از بازه قبلی، در این بازه موفق شده است'),
            'completed' => $this->ceoInitDealStatusCard('previous_range_completed_deals', 'اتمام'),
            'outstanding' => $this->ceoInitDealStatusCard('previous_range_outstanding_deals', 'مانده حساب'),
        ];
        $leadMix = [
            'new_leads' => ['key' => 'new_leads', 'label' => 'لید جدید', 'count' => 0, 'percent' => 0.0, 'deal_ids' => []],
            'old_previous_purchase' => ['key' => 'old_previous_purchase', 'label' => 'لید قدیمی، قبلا خرید داشته', 'count' => 0, 'percent' => 0.0, 'deal_ids' => []],
            'old_without_purchase' => ['key' => 'old_without_purchase', 'label' => 'لید قدیمی، قبلا خرید نداشته', 'count' => 0, 'percent' => 0.0, 'deal_ids' => []],
        ];

        foreach ($rows as $row) {
            $dealId = (int)($row['deal_id'] ?? 0);
            if ($dealId <= 0) {
                continue;
            }
            $status = (string)($row['status'] ?? '');
            $isWon = $status === 'Won';
            $amount = (float)($row['payable_amount'] ?? 0);
            $paid = (float)($row['total_paid'] ?? 0);
            $paymentOverlay = $paymentOverlayByDeal[$dealId] ?? null;
            $paymentInRangeAmount = is_array($paymentOverlay)
                ? (float)($paymentOverlay['payment_in_range_amount'] ?? 0)
                : (float)($row['payment_in_range_amount'] ?? 0);
            $salesDepositInRangeAmount = is_array($paymentOverlay)
                ? (float)($paymentOverlay['category_amounts']['sales_deposit'] ?? 0)
                : $paymentInRangeAmount;
            $hasSudranInRange = (is_array($paymentOverlay) && !empty($paymentOverlay['has_sudran_in_range']))
                || (int)($row['has_sudran_in_range'] ?? 0) === 1;
            $creatorRole = strtolower(trim((string)($row['creator_role'] ?? '')));
            $submittedByAgent = $creatorRole === 'agent';
            $submittedByCrm = !$submittedByAgent;
            $activityCount = (int)($row['activity_count'] ?? 0);
            $noAnswerCount = (int)($row['no_answer_count'] ?? 0);
            $successActivityCount = (int)($row['successful_activity_count'] ?? 0);
            $dealDate = (string)($row['deal_date'] ?? '');
            $firstPaymentDate = (string)($row['first_payment_date'] ?? '');
            $registeredInRange = $this->uiOracleInRange($dealDate, ['date_from' => $dateFrom, 'date_to' => $dateTo]);
            $firstPaymentInRange = $this->uiOracleInRange($firstPaymentDate, ['date_from' => $dateFrom, 'date_to' => $dateTo]);
            $outsideRegisteredFirstPaymentInRange = !$registeredInRange && $firstPaymentInRange;
            if (!$registeredInRange && !$outsideRegisteredFirstPaymentInRange) {
                continue;
            }
            $isCompleted = $paid > 0 && ($amount <= 0 || $paid >= $amount);
            $isOutstanding = $paid > 0 && !$isCompleted;
            $isReportingSuccessful = $status !== 'Lost' && $paid > 0 && $firstPaymentInRange;
            if ($outsideRegisteredFirstPaymentInRange && $isReportingSuccessful) {
                $this->ceoAddDealStatusCard($previousSuccess['total'], $dealId, $salesDepositInRangeAmount, $submittedByAgent, $submittedByCrm, $hasSudranInRange);
                if ($isCompleted) {
                    $this->ceoAddDealStatusCard($previousSuccess['completed'], $dealId, $salesDepositInRangeAmount, $submittedByAgent, $submittedByCrm, $hasSudranInRange);
                }
                if ($isOutstanding) {
                    $this->ceoAddDealStatusCard($previousSuccess['outstanding'], $dealId, $salesDepositInRangeAmount, $submittedByAgent, $submittedByCrm, $hasSudranInRange);
                }
            }
            if (!$registeredInRange) {
                continue;
            }
            $this->ceoAddDealStatusCard($cards['total_registered_deals'], $dealId, $paymentInRangeAmount, $submittedByAgent, $submittedByCrm, $hasSudranInRange);
            if ($activityCount === 0) {
                $this->ceoAddDealStatusCard($cards['no_activity_deals'], $dealId, $paymentInRangeAmount, $submittedByAgent, $submittedByCrm, $hasSudranInRange);
            } elseif ($noAnswerCount > 0 && $successActivityCount === 0) {
                $this->ceoAddDealStatusCard($cards['no_answer_only_deals'], $dealId, $paymentInRangeAmount, $submittedByAgent, $submittedByCrm, $hasSudranInRange);
            }
            if ($paid <= 0 && $status !== 'Lost' && $successActivityCount > 0) {
                $this->ceoAddDealStatusCard($cards['follow_up_deals'], $dealId, $paymentInRangeAmount, $submittedByAgent, $submittedByCrm, $hasSudranInRange);
            }
            if ($status === 'Lost') {
                $this->ceoAddDealStatusCard($cards['lost_deals'], $dealId, $paymentInRangeAmount, $submittedByAgent, $submittedByCrm, $hasSudranInRange);
            }
            if ($isReportingSuccessful && $isCompleted) {
                $this->ceoAddDealStatusCard($cards['won_deals'], $dealId, $salesDepositInRangeAmount, $submittedByAgent, $submittedByCrm, $hasSudranInRange);
            }
            if ($isReportingSuccessful && $isOutstanding) {
                $this->ceoAddDealStatusCard($cards['won_outstanding_deals'], $dealId, $salesDepositInRangeAmount, $submittedByAgent, $submittedByCrm, $hasSudranInRange);
            }
            $firstDealDate = (string)($row['first_deal_date'] ?? '');
            $leadDate = (string)($row['lead_register_date'] ?? '');
            $isNew = $firstDealDate !== '' && $dealDate === $firstDealDate && ($leadDate === '' || $leadDate >= $firstDealDate);
            if ($isNew) {
                $leadMix['new_leads']['count']++;
                $leadMix['new_leads']['deal_ids'][$dealId] = true;
            } else {
                $saleStatus = trim((string)($row['lead_sale_status'] ?? ''));
                $hadPurchase = (int)($row['has_previous_purchase'] ?? 0) === 1
                    || in_array($saleStatus, ['1', 'موفق', 'Won', 'won', 'success', 'successful'], true);
                $mixKey = $hadPurchase ? 'old_previous_purchase' : 'old_without_purchase';
                $leadMix[$mixKey]['count']++;
                $leadMix[$mixKey]['deal_ids'][$dealId] = true;
            }
        }

        $totalDeals = (int)$cards['total_registered_deals']['count'];
        foreach ($cards as $key => $row) {
            $cards[$key]['percent'] = $totalDeals > 0 ? round(((int)$row['count'] / $totalDeals) * 100, 2) : 0.0;
            unset($cards[$key]['deal_ids']);
        }
        $previousTotal = (int)$previousSuccess['total']['count'];
        foreach ($previousSuccess as $key => $row) {
            $previousSuccess[$key]['percent'] = $previousTotal > 0 ? round(((int)$row['count'] / $previousTotal) * 100, 2) : 0.0;
            unset($previousSuccess[$key]['deal_ids']);
        }
        foreach ($leadMix as $key => $row) {
            $leadMix[$key]['percent'] = $totalDeals > 0 ? round(((int)$row['count'] / $totalDeals) * 100, 2) : 0.0;
            unset($leadMix[$key]['deal_ids']);
        }

        return [
            'total_deals' => $totalDeals,
            'cards' => array_values($cards),
            'lead_mix' => array_values($leadMix),
            'previous_success' => $previousSuccess,
        ];
    }

    private function ceoInitDealStatusCard(string $key, string $label): array
    {
        return [
            'key' => $key,
            'label' => $label,
            'count' => 0,
            'amount' => 0.0,
            'agent_deal_count' => 0,
            'crm_deal_count' => 0,
            'sudran_deal_count' => 0,
            'deal_ids' => [],
        ];
    }

    private function ceoAddDealStatusCard(array &$card, int $dealId, float $amount, bool $submittedByAgent = false, bool $submittedByCrm = false, bool $hasSudranPayment = false): void
    {
        if (isset($card['deal_ids'][$dealId])) {
            return;
        }
        $card['count'] = (int)($card['count'] ?? 0) + 1;
        $card['amount'] = (float)($card['amount'] ?? 0) + $amount;
        if ($submittedByAgent) {
            $card['agent_deal_count'] = (int)($card['agent_deal_count'] ?? 0) + 1;
        }
        if ($submittedByCrm) {
            $card['crm_deal_count'] = (int)($card['crm_deal_count'] ?? 0) + 1;
        }
        if ($hasSudranPayment) {
            $card['sudran_deal_count'] = (int)($card['sudran_deal_count'] ?? 0) + 1;
        }
        $card['deal_ids'][$dealId] = true;
    }

    private function buildCeoPaymentOverlayByDeal(array $filters): array
    {
        $overlay = [];
        foreach ($this->getCeoPaymentRows($filters) as $row) {
            if ($this->classifyCeoPaymentRow($row, $filters) === '') {
                continue;
            }
            $dealId = (int)($row['deal_id'] ?? 0);
            if ($dealId <= 0) {
                continue;
            }
            if (!isset($overlay[$dealId])) {
                $overlay[$dealId] = [
                    'payment_in_range_amount' => 0.0,
                    'payment_count' => 0,
                    'has_sudran_in_range' => false,
                    'category_amounts' => [],
                ];
            }
            $amount = (float)($row['net_amount'] ?? 0);
            $category = $this->classifyCeoPaymentRow($row, $filters);
            $overlay[$dealId]['payment_in_range_amount'] += $amount;
            $overlay[$dealId]['payment_count']++;
            $overlay[$dealId]['category_amounts'][$category] = (float)($overlay[$dealId]['category_amounts'][$category] ?? 0) + $amount;
            if ((int)($row['is_sudran'] ?? 0) === 1) {
                $overlay[$dealId]['has_sudran_in_range'] = true;
            }
        }
        return $overlay;
    }

    private function tableHasColumns(string $table, array $columns): bool
    {
        $available = $this->db->getTableColumns($table);
        if (empty($available)) {
            return false;
        }
        foreach ($columns as $column) {
            if (empty($available[$column])) {
                return false;
            }
        }
        return true;
    }

    private function ceoPaymentDateExpr(string $alias = 'pr'): string
    {
        return "DATE(COALESCE(
            CASE WHEN {$alias}.cheque_collection_status = 'collected' THEN {$alias}.cheque_collection_date ELSE NULL END,
            {$alias}.effective_date,
            {$alias}.deposit_date,
            {$alias}.payment_date,
            {$alias}.created_at
        ))";
    }

    private function ceoPaymentRegisteredDateExpr(string $alias = 'pr'): string
    {
        return "DATE(COALESCE({$alias}.registered_at, {$alias}.created_at))";
    }

    private function ceoPaymentNetAmountExpr(string $alias = 'pr'): string
    {
        return "GREATEST(COALESCE({$alias}.amount, 0) - COALESCE({$alias}.refunded_amount, 0), 0)";
    }

    private function ceoPaymentIsChequeExpr(string $alias = 'pr'): string
    {
        return "(
            COALESCE({$alias}.cheque_status, 'not_cheque') <> 'not_cheque'
            OR COALESCE({$alias}.cheque_collection_status, 'not_cheque') <> 'not_cheque'
            OR LOWER(COALESCE({$alias}.method, '')) IN ('check', 'cheque')
            OR COALESCE({$alias}.method, '') LIKE '%چک%'
        )";
    }

    private function ceoPaymentIsSudranExpr(string $alias = 'pr'): string
    {
        return "(
            LOWER(REPLACE(REPLACE(REPLACE(TRIM(COALESCE({$alias}.method, '')), ' ', '_'), '-', '_'), '/', '_'))
                IN ('sudran_payment_gateway', 'sudran_gateway', 'soodran_payment_gateway', 'soodran_gateway', 'sodran_payment_gateway')
            OR COALESCE({$alias}.method, '') LIKE '%سودران%'
            OR LOWER(COALESCE({$alias}.raw_snapshot_json, '')) LIKE '%sudran%'
            OR LOWER(COALESCE({$alias}.raw_snapshot_json, '')) LIKE '%soodran%'
            OR LOWER(COALESCE({$alias}.raw_snapshot_json, '')) LIKE '%sodran%'
            OR COALESCE({$alias}.raw_snapshot_json, '') LIKE '%سودران%'
        )";
    }

    private function ceoPaymentValidWhere(string $alias = 'pr'): string
    {
        return "COALESCE({$alias}.is_deleted_placeholder, 0) = 0
            AND COALESCE({$alias}.amount, 0) > 0
            AND COALESCE({$alias}.refund_status, 'NONE') <> 'FULL'
            AND NOT EXISTS (
                SELECT 1
                FROM deals d_payment_state
                WHERE d_payment_state.id = {$alias}.deal_id
                  AND COALESCE({$alias}.payment_sequence, 0) > 0
                  AND COALESCE(d_payment_state.payments, '') <> ''
                  AND JSON_VALID(d_payment_state.payments)
                  AND (
                      LOWER(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d_payment_state.payments, CONCAT('$[', CAST({$alias}.payment_sequence AS SIGNED) - 1, '].deleted'))), '')) IN ('true', '1', 'yes')
                      OR LOWER(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d_payment_state.payments, CONCAT('$[', CAST({$alias}.payment_sequence AS SIGNED) - 1, '].is_deleted'))), '')) IN ('true', '1', 'yes')
                      OR (
                          COALESCE(CAST(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(d_payment_state.payments, CONCAT('$[', CAST({$alias}.payment_sequence AS SIGNED) - 1, '].amount'))), '') AS DECIMAL(18,2)), {$alias}.amount) <= 0
                          AND (
                              COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d_payment_state.payments, CONCAT('$[', CAST({$alias}.payment_sequence AS SIGNED) - 1, '].deleted_at'))), '') <> ''
                              OR COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d_payment_state.payments, CONCAT('$[', CAST({$alias}.payment_sequence AS SIGNED) - 1, '].deleted_reason'))), '') <> ''
                              OR COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d_payment_state.payments, CONCAT('$[', CAST({$alias}.payment_sequence AS SIGNED) - 1, '].delete_reason'))), '') <> ''
                              OR COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d_payment_state.payments, CONCAT('$[', CAST({$alias}.payment_sequence AS SIGNED) - 1, '].deletion_reason'))), '') <> ''
                          )
                      )
                  )
            )";
    }

    private function ceoPreferredTransactionLinkSubquery(): string
    {
        return "
            SELECT
                prl.payment_record_id,
                COALESCE(
                    MIN(CASE
                        WHEN tx.id IS NOT NULL
                         AND LOWER(COALESCE(tx.status, 'confirmed')) NOT IN ('cancelled', 'canceled', 'deleted', 'void')
                        THEN tx.id
                    END),
                    MIN(CAST(prl.linked_id AS UNSIGNED))
                ) as transaction_id
            FROM payment_record_links prl
            LEFT JOIN transactions tx ON tx.id = CAST(prl.linked_id AS UNSIGNED)
            WHERE prl.linked_table = 'transactions'
              AND prl.link_type = 'PROJECTION'
            GROUP BY prl.payment_record_id
        ";
    }

    private function getCeoPaymentRows(array $filters): array
    {
        [$dateFrom, $dateTo] = $this->dateRangeFromFilters($filters);
        return $this->getCeoPaymentRowsBetween($filters, $dateFrom, $dateTo);
    }

    private function getCeoLeanPaymentRowsBetween(array $filters, string $dateFrom, string $dateTo, array $dealIds = []): array
    {
        $dealIds = array_values(array_unique(array_filter(array_map('intval', $dealIds))));
        $useDealIdSql = !empty($dealIds) && count($dealIds) <= 1000;
        $dealIdSql = '';
        $dealIdSqlFirst = '';
        $dealIdParams = [];
        if ($useDealIdSql) {
            $dealIdSql = ' AND d.id IN (' . implode(',', array_fill(0, count($dealIds), '?')) . ')';
            $dealIdSqlFirst = ' AND d2.id IN (' . implode(',', array_fill(0, count($dealIds), '?')) . ')';
            $dealIdParams = $dealIds;
        }
        $dealWhere = $this->buildWhereClauseForDealsNoDate($filters, 'd');
        $dealParams = $this->buildParamsForDealsNoDate($filters);
        $dealWhereFirst = $this->buildWhereClauseForDealsNoDate($filters, 'd2');
        $dealParamsFirst = $this->buildParamsForDealsNoDate($filters);
        $paymentDate = $this->ceoPaymentDateExpr('pr');
        $firstPaymentDate = $this->ceoPaymentDateExpr('pr2');
        $netAmount = $this->ceoPaymentNetAmountExpr('pr');
        $isCheque = $this->ceoPaymentIsChequeExpr('pr');
        $isChequeFirst = $this->ceoPaymentIsChequeExpr('pr2');
        $isSudran = $this->ceoPaymentIsSudranExpr('pr');
        $isSudranFirst = $this->ceoPaymentIsSudranExpr('pr2');
        $valid = $this->ceoPaymentValidWhere('pr');
        $validFirst = $this->ceoPaymentValidWhere('pr2');

        $rows = $this->runSelectAll("
            SELECT
                pr.id as payment_record_id,
                pr.source_hash,
                pr.deal_id,
                pr.deal_didar_id,
                pr.payment_uuid,
                pr.payment_sequence,
                pr.source_type,
                pr.method,
                pr.amount as gross_amount,
                pr.refunded_amount,
                pr.refund_status,
                pr.commission_status,
                {$netAmount} as net_amount,
                {$paymentDate} as payment_window_date,
                fp.first_payment_date,
                CASE WHEN {$isCheque} THEN 1 ELSE 0 END as is_cheque,
                CASE WHEN {$isSudran} THEN 1 ELSE 0 END as is_sudran,
                COALESCE(d.status, '') as deal_status,
                COALESCE(pr.cheque_status, 'not_cheque') as cheque_status,
                COALESCE(pr.cheque_collection_status, 'not_cheque') as cheque_collection_status,
                pr.cheque_due_date,
                pr.cheque_collection_date
            FROM payment_records pr
            INNER JOIN deals d ON d.id = pr.deal_id
            LEFT JOIN (
                SELECT pr2.deal_id, MIN({$firstPaymentDate}) as first_payment_date
                FROM payment_records pr2
                INNER JOIN deals d2 ON d2.id = pr2.deal_id
                WHERE {$validFirst}
                  AND {$firstPaymentDate} IS NOT NULL
                  AND ({$isChequeFirst} = 0 OR pr2.cheque_collection_status = 'collected')
                  AND {$isSudranFirst} = 0
                  AND {$dealWhereFirst}
                  {$dealIdSqlFirst}
                GROUP BY pr2.deal_id
            ) fp ON fp.deal_id = pr.deal_id
            WHERE {$valid}
              AND {$netAmount} > 0
              AND {$paymentDate} BETWEEN ? AND ?
              AND ({$isCheque} = 0 OR pr.cheque_collection_status = 'collected')
              AND {$dealWhere}
              {$dealIdSql}
            ORDER BY {$paymentDate} DESC, pr.id DESC
        ", array_merge($dealParamsFirst, $dealIdParams, [$dateFrom, $dateTo], $dealParams, $dealIdParams));

        return $this->dedupeCeoPaymentRows($this->mergeCeoDealJsonFallbackRows($rows, $filters, $dateFrom, $dateTo, $dealIds, true));
    }

    private function getCeoPaymentRowsBetween(array $filters, string $dateFrom, string $dateTo, array $dealIds = []): array
    {
        $dealIds = array_values(array_unique(array_filter(array_map('intval', $dealIds))));
        $dealIdSql = '';
        $dealIdSqlFirst = '';
        $dealIdParams = [];
        if (!empty($dealIds)) {
            $dealIdSql = ' AND d.id IN (' . implode(',', array_fill(0, count($dealIds), '?')) . ')';
            $dealIdSqlFirst = ' AND d2.id IN (' . implode(',', array_fill(0, count($dealIds), '?')) . ')';
            $dealIdParams = $dealIds;
        }
        $dealWhere = $this->buildWhereClauseForDealsNoDate($filters, 'd');
        $dealParams = $this->buildParamsForDealsNoDate($filters);
        $dealWhereFirst = $this->buildWhereClauseForDealsNoDate($filters, 'd2');
        $dealParamsFirst = $this->buildParamsForDealsNoDate($filters);
        $paymentDate = $this->ceoPaymentDateExpr('pr');
        $firstPaymentDate = $this->ceoPaymentDateExpr('pr2');
        $netAmount = $this->ceoPaymentNetAmountExpr('pr');
        $isCheque = $this->ceoPaymentIsChequeExpr('pr');
        $isChequeFirst = $this->ceoPaymentIsChequeExpr('pr2');
        $isSudran = $this->ceoPaymentIsSudranExpr('pr');
        $isSudranFirst = $this->ceoPaymentIsSudranExpr('pr2');
        $valid = $this->ceoPaymentValidWhere('pr');
        $validFirst = $this->ceoPaymentValidWhere('pr2');
        $primaryProductSql = $this->buildPrimaryDealProductSql();
        $hintExpr = $this->buildDealProductHintExpr('d');
        $productNameExpr = $this->buildResolvedProductNameExpr($hintExpr, 'p', 'dp');

        $sql = "
            SELECT
                pr.id as payment_record_id,
                pr.source_hash,
                pr.deal_id,
                pr.deal_didar_id,
                pr.payment_uuid,
                pr.payment_sequence,
                pr.source_type,
                pr.method,
                pr.amount as gross_amount,
                pr.refunded_amount,
                pr.refund_status,
                pr.payment_date as payment_record_payment_date,
                pr.deposit_date,
                pr.effective_date,
                pr.registered_at,
                pr.created_at,
                pr.payment_owner_user_id,
                pr.payment_owner_didar_id,
                pr.payment_owner_name_snapshot,
                pr.payment_owner_source,
                pr.finance_status,
                pr.sepidar_registered_at,
                pr.commission_status,
                pr.raw_snapshot_json,
                {$netAmount} as net_amount,
                {$paymentDate} as payment_window_date,
                {$this->ceoPaymentRegisteredDateExpr('pr')} as registered_date,
                fp.first_payment_date,
                CASE WHEN {$isCheque} THEN 1 ELSE 0 END as is_cheque,
                CASE WHEN {$isSudran} THEN 1 ELSE 0 END as is_sudran,
                COALESCE(d.status, '') as deal_status,
                COALESCE(pr.cheque_status, 'not_cheque') as cheque_status,
                COALESCE(pr.cheque_collection_status, 'not_cheque') as cheque_collection_status,
                pr.cheque_due_date,
                pr.cheque_collection_date,
                tx.id as transaction_id,
                COALESCE(tx.status, '') as tx_payment_status,
                COALESCE(tx.card_last4, '') as tx_card_last4,
                COALESCE(tx.destination_bank, '') as tx_destination_bank,
                COALESCE(tx.destination_card_number, '') as tx_destination_card_number,
                COALESCE(tx.destination_bank_title, '') as tx_destination_bank_title,
                COALESCE(tx.destination_card_full, '') as tx_destination_card_full,
                COALESCE(tx.destination_account_number, '') as tx_destination_account_number,
                COALESCE(tx.destination_sheba, '') as tx_destination_sheba,
                COALESCE(tx.destination_tafsili_code, '') as tx_destination_tafsili_code,
                COALESCE(tx.destination_account_owner, '') as tx_destination_account_owner,
                COALESCE(tx.account_owner_name, '') as tx_account_owner_name,
                COALESCE(tx.depositor_name, '') as tx_depositor_name,
                COALESCE(tx.tracking_code_last4, '') as tx_tracking_code_last4,
                COALESCE(tx.check_owner_name, '') as tx_check_owner_name,
                COALESCE(tx.check_bank_name, '') as tx_check_bank_name,
                COALESCE(tx.check_number, '') as tx_check_number,
                tx.check_date as tx_check_date,
                tx.check_due_date as tx_check_due_date,
                COALESCE(tx.sayadi_date, '') as tx_sayadi_date,
                tx.check_delivery_date as tx_check_delivery_date,
                COALESCE(tx.check_recipient_name, '') as tx_check_recipient_name,
                COALESCE(tx.receipt_image_path, '') as tx_receipt_image_path,
                COALESCE(tx.check_image_path, '') as tx_check_image_path,
                COALESCE(tx.registered_by_didar_id, '') as tx_registered_by_didar_id,
                COALESCE(tx.registered_by_name, '') as tx_registered_by_name,
                COALESCE(tx.refund_amount, 0) as tx_refund_amount,
                COALESCE(tx.refund_count, 0) as tx_refund_count,
                COALESCE(tx.refund_description, '') as tx_refund_description,
                COALESCE(tx.description, '') as tx_description,
                COALESCE(tx.invoice_code, '') as tx_invoice_code,
                DATE(d.register_time) as deal_registered_date,
                COALESCE(d.title, '') as deal_title,
                COALESCE(d.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name,
                COALESCE(d.created_by_didar_id, '') as deal_created_by_didar_id,
                COALESCE(cu.role, '') as deal_creator_role,
                COALESCE(d.created_via, '') as deal_created_via,
                {$productNameExpr} as product_name,
                COALESCE(NULLIF(TRIM(d.acquaintance_source), ''), NULLIF(TRIM(pe.acquaintance_source), ''), 'نامشخص') as acquaintance_source,
                COALESCE(NULLIF(TRIM(d.acquaintance_detail), ''), NULLIF(TRIM(pe.acquaintance_detail), ''), 'بدون جزئیات') as acquaintance_detail,
                COALESCE(NULLIF(TRIM(d.content_topic), ''), NULLIF(TRIM(pe.content_topic), ''), 'بدون موضوع') as content_topic
            FROM payment_records pr
            INNER JOIN deals d ON d.id = pr.deal_id
            LEFT JOIN ({$this->ceoPreferredTransactionLinkSubquery()}) prl ON prl.payment_record_id = pr.id
            LEFT JOIN transactions tx ON tx.id = prl.transaction_id
            LEFT JOIN users u ON u.didar_user_id = d.owner_didar_id
            LEFT JOIN users cu ON cu.didar_user_id = d.created_by_didar_id
            LEFT JOIN persons pe ON pe.didar_contact_id = d.contact_didar_id
            LEFT JOIN ({$primaryProductSql}) dp ON dp.deal_id = d.id
            LEFT JOIN products p ON p.id = dp.product_id
            LEFT JOIN (
                SELECT pr2.deal_id, MIN({$firstPaymentDate}) as first_payment_date
                FROM payment_records pr2
                INNER JOIN deals d2 ON d2.id = pr2.deal_id
                WHERE {$validFirst}
                  AND {$firstPaymentDate} IS NOT NULL
                  AND ({$isChequeFirst} = 0 OR pr2.cheque_collection_status = 'collected')
                  AND {$isSudranFirst} = 0
                  AND {$dealWhereFirst}
                  {$dealIdSqlFirst}
                GROUP BY pr2.deal_id
            ) fp ON fp.deal_id = pr.deal_id
            WHERE {$valid}
              AND {$netAmount} > 0
              AND {$paymentDate} BETWEEN ? AND ?
              AND ({$isCheque} = 0 OR pr.cheque_collection_status = 'collected')
              AND {$dealWhere}
              {$dealIdSql}
            ORDER BY {$paymentDate} DESC, pr.id DESC
        ";

        $rows = $this->runSelectAll($sql, array_merge($dealParamsFirst, $dealIdParams, [$dateFrom, $dateTo], $dealParams, $dealIdParams));
        return $this->dedupeCeoPaymentRows($this->mergeCeoDealJsonFallbackRows($rows, $filters, $dateFrom, $dateTo, $dealIds));
    }

    private function mergeCeoDealJsonFallbackRows(array $paymentRows, array $filters, string $dateFrom, string $dateTo, array $dealIds = [], bool $leanFallback = false): array
    {
        $dealIds = array_values(array_unique(array_filter(array_map('intval', $dealIds))));
        if ($leanFallback) {
            $existingKeys = [];
            foreach ($paymentRows as $row) {
                $dealId = (int)($row['deal_id'] ?? 0);
                $sequence = (int)($row['payment_sequence'] ?? 0);
                $amount = (float)($row['net_amount'] ?? 0);
                $date = trim((string)($row['payment_window_date'] ?? ''));
                $method = $this->normalizeCeoPaymentMethodCode((string)($row['method'] ?? ''));
                if ($dealId > 0 && $sequence > 0) {
                    $existingKeys['seq:' . $dealId . ':' . $sequence] = true;
                }
                if ($dealId > 0 && $date !== '' && $amount > 0) {
                    $existingKeys['fingerprint:' . $dealId . ':' . $date . ':' . $this->ceoMoneyKey($amount) . ':' . $method] = true;
                }
                if ($dealId > 0 && $sequence <= 0 && $amount > 0 && $method !== '') {
                    $existingKeys['legacy_amount_method:' . $dealId . ':' . $this->ceoMoneyKey($amount) . ':' . $method] = true;
                }
                if ((int)($row['is_sudran'] ?? 0) === 1 && $dealId > 0 && $amount > 0) {
                    $existingKeys['sudran_deal_amount:' . $dealId . ':' . $this->ceoMoneyKey($amount)] = true;
                }
            }

            $fallbackRows = [];
            if (count($dealIds) > 1000) {
                foreach (array_chunk($dealIds, 500) as $dealIdChunk) {
                    $fallbackRows = array_merge($fallbackRows, $this->getCeoDealJsonFallbackRows($filters, $dateFrom, $dateTo, $existingKeys, $dealIdChunk, true));
                }
            } else {
                $fallbackRows = $this->getCeoDealJsonFallbackRows($filters, $dateFrom, $dateTo, $existingKeys, $dealIds, true);
            }
            return !empty($fallbackRows) ? array_merge($paymentRows, $fallbackRows) : $paymentRows;
        }

        $jsonRows = [];
        if (count($dealIds) > 1000) {
            foreach (array_chunk($dealIds, 500) as $dealIdChunk) {
                $jsonRows = array_merge($jsonRows, $this->getCeoDealJsonFallbackRows($filters, $dateFrom, $dateTo, [], $dealIdChunk, $leanFallback));
            }
        } else {
            $jsonRows = $this->getCeoDealJsonFallbackRows($filters, $dateFrom, $dateTo, [], $dealIds, $leanFallback);
        }
        $jsonDealIds = [];
        $jsonKeys = [];
        foreach ($jsonRows as $row) {
            $dealId = (int)($row['deal_id'] ?? 0);
            $sequence = (int)($row['payment_sequence'] ?? 0);
            $amount = (float)($row['net_amount'] ?? 0);
            $date = trim((string)($row['payment_window_date'] ?? ''));
            $method = $this->normalizeCeoPaymentMethodCode((string)($row['method'] ?? ''));
            if ($dealId <= 0 || $amount <= 0) {
                continue;
            }
            $jsonDealIds[$dealId] = true;
            if ($sequence > 0) {
                $jsonKeys['seq:' . $dealId . ':' . $sequence] = true;
            }
            if ($date !== '') {
                $jsonKeys['fingerprint:' . $dealId . ':' . $date . ':' . $this->ceoMoneyKey($amount) . ':' . $method] = true;
                $jsonKeys['date_amount:' . $dealId . ':' . $date . ':' . $this->ceoMoneyKey($amount)] = true;
            }
        }

        $paymentRows = array_values(array_filter($paymentRows, function ($row) use ($jsonDealIds, $jsonKeys) {
            if ((int)($row['is_sudran'] ?? 0) !== 1) {
                return true;
            }
            $dealId = (int)($row['deal_id'] ?? 0);
            if ($dealId <= 0 || !isset($jsonDealIds[$dealId])) {
                return true;
            }
            $sequence = (int)($row['payment_sequence'] ?? 0);
            $amount = (float)($row['net_amount'] ?? 0);
            $date = trim((string)($row['payment_window_date'] ?? ''));
            $method = $this->normalizeCeoPaymentMethodCode((string)($row['method'] ?? ''));
            if ($sequence > 0 && isset($jsonKeys['seq:' . $dealId . ':' . $sequence])) {
                return true;
            }
            if ($date !== '' && $amount > 0 && isset($jsonKeys['fingerprint:' . $dealId . ':' . $date . ':' . $this->ceoMoneyKey($amount) . ':' . $method])) {
                return true;
            }
            if ($date !== '' && $amount > 0 && isset($jsonKeys['date_amount:' . $dealId . ':' . $date . ':' . $this->ceoMoneyKey($amount)])) {
                return true;
            }
            return false;
        }));

        $existingKeys = [];
        foreach ($paymentRows as $row) {
            $dealId = (int)($row['deal_id'] ?? 0);
            $sequence = (int)($row['payment_sequence'] ?? 0);
            $amount = (float)($row['net_amount'] ?? 0);
            $date = trim((string)($row['payment_window_date'] ?? ''));
            $method = $this->normalizeCeoPaymentMethodCode((string)($row['method'] ?? ''));
            $isSudran = (int)($row['is_sudran'] ?? 0) === 1;
            if ($dealId > 0 && $sequence > 0) {
                $existingKeys['seq:' . $dealId . ':' . $sequence] = true;
            }
            if ($dealId > 0 && $date !== '' && $amount > 0) {
                $existingKeys['fingerprint:' . $dealId . ':' . $date . ':' . $this->ceoMoneyKey($amount) . ':' . $method] = true;
            }
            if ($dealId > 0 && $sequence <= 0 && $amount > 0 && $method !== '') {
                $existingKeys['legacy_amount_method:' . $dealId . ':' . $this->ceoMoneyKey($amount) . ':' . $method] = true;
            }
            if ($isSudran && $dealId > 0 && $amount > 0) {
                $existingKeys['sudran_deal_amount:' . $dealId . ':' . $this->ceoMoneyKey($amount)] = true;
            }
        }

        $fallbackRows = [];
        foreach ($jsonRows as $row) {
            $dealId = (int)($row['deal_id'] ?? 0);
            $sequence = (int)($row['payment_sequence'] ?? 0);
            $amount = (float)($row['net_amount'] ?? 0);
            $date = trim((string)($row['payment_window_date'] ?? ''));
            $method = $this->normalizeCeoPaymentMethodCode((string)($row['method'] ?? ''));
            if (isset($existingKeys['seq:' . $dealId . ':' . $sequence])
                || isset($existingKeys['fingerprint:' . $dealId . ':' . $date . ':' . $this->ceoMoneyKey($amount) . ':' . $method])
                || isset($existingKeys['legacy_amount_method:' . $dealId . ':' . $this->ceoMoneyKey($amount) . ':' . $method])
                || isset($existingKeys['sudran_deal_amount:' . $dealId . ':' . $this->ceoMoneyKey($amount)])) {
                continue;
            }
            $fallbackRows[] = $row;
        }
        if (!$fallbackRows) {
            return $paymentRows;
        }
        $merged = array_merge($paymentRows, $fallbackRows);
        usort($merged, static function ($a, $b) {
            $dateCmp = strcmp((string)($b['payment_window_date'] ?? ''), (string)($a['payment_window_date'] ?? ''));
            if ($dateCmp !== 0) {
                return $dateCmp;
            }
            return (int)($b['payment_record_id'] ?? 0) <=> (int)($a['payment_record_id'] ?? 0);
        });
        return $merged;
    }

    private function dedupeCeoPaymentRows(array $paymentRows): array
    {
        $bySequence = [];
        $result = [];
        foreach ($paymentRows as $idx => $row) {
            $dealId = (int)($row['deal_id'] ?? 0);
            $sequence = (int)($row['payment_sequence'] ?? 0);
            if ($dealId > 0 && $sequence > 0) {
                $key = $dealId . ':' . $sequence;
                if (!isset($bySequence[$key])) {
                    $bySequence[$key] = $idx;
                    $result[$idx] = $row;
                    continue;
                }
                $existingIdx = $bySequence[$key];
                $existing = $result[$existingIdx] ?? [];
                if ($this->scoreCeoPaymentProjection($row) > $this->scoreCeoPaymentProjection($existing)) {
                    unset($result[$existingIdx]);
                    $bySequence[$key] = $idx;
                    $result[$idx] = $row;
                }
                continue;
            }
            $result[$idx] = $row;
        }
        return array_values($result);
    }

    private function scoreCeoPaymentProjection(array $row): int
    {
        $score = 0;
        $sourceType = strtoupper(trim((string)($row['source_type'] ?? '')));
        $commissionStatus = strtoupper(trim((string)($row['commission_status'] ?? '')));
        if ($sourceType === 'DEAL_JSON') {
            $score += 40;
        } elseif ($sourceType === 'DEAL_JSON_FALLBACK') {
            $score += 30;
        } elseif ($sourceType === 'TRANSACTION') {
            $score += 20;
        }
        if ($commissionStatus !== 'VOID') {
            $score += 5;
        }
        if ((int)($row['transaction_id'] ?? 0) > 0) {
            $score += 2;
        }
        if ((int)($row['payment_record_id'] ?? 0) > 0) {
            $score += 1;
        }
        return $score;
    }

    private function getCeoDealJsonFallbackRows(array $filters, string $dateFrom, string $dateTo, array $existingKeys, array $dealIds = [], bool $lean = false): array
    {
        $dealIds = array_values(array_unique(array_filter(array_map('intval', $dealIds))));
        $dealIdSql = '';
        $dealIdParams = [];
        if (!empty($dealIds)) {
            $dealIdSql = ' AND d.id IN (' . implode(',', array_fill(0, count($dealIds), '?')) . ')';
            $dealIdParams = $dealIds;
        }
        $dealWhere = $this->buildWhereClauseForDealsNoDate($filters, 'd');
        $dealParams = $this->buildParamsForDealsNoDate($filters);
        $primaryProductSql = $this->buildPrimaryDealProductSql();
        $hintExpr = $this->buildDealProductHintExpr('d');
        $productNameExpr = $this->buildResolvedProductNameExpr($hintExpr, 'p', 'dp');
        $rows = $this->runSelectAll("
            SELECT
                d.id as deal_id,
                d.didar_deal_id,
                d.payments as payments_json,
                DATE(COALESCE(d.register_time, d.created_at)) as deal_registered_date,
                COALESCE(d.status, '') as deal_status,
                COALESCE(d.title, '') as deal_title,
                COALESCE(d.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name,
                COALESCE(d.created_by_didar_id, '') as deal_created_by_didar_id,
                COALESCE(cu.role, '') as deal_creator_role,
                COALESCE(d.created_via, '') as deal_created_via,
                {$productNameExpr} as product_name,
                COALESCE(NULLIF(TRIM(d.acquaintance_source), ''), NULLIF(TRIM(pe.acquaintance_source), ''), 'نامشخص') as acquaintance_source,
                COALESCE(NULLIF(TRIM(d.acquaintance_detail), ''), NULLIF(TRIM(pe.acquaintance_detail), ''), 'بدون جزئیات') as acquaintance_detail,
                COALESCE(NULLIF(TRIM(d.content_topic), ''), NULLIF(TRIM(pe.content_topic), ''), 'بدون موضوع') as content_topic
            FROM deals d
            LEFT JOIN users u ON u.didar_user_id = d.owner_didar_id
            LEFT JOIN users cu ON cu.didar_user_id = d.created_by_didar_id
            LEFT JOIN persons pe ON pe.didar_contact_id = d.contact_didar_id
            LEFT JOIN ({$primaryProductSql}) dp ON dp.deal_id = d.id
            LEFT JOIN products p ON p.id = dp.product_id
            WHERE {$dealWhere}
              AND COALESCE(d.payments, '') <> ''
              AND JSON_VALID(d.payments)
              AND COALESCE(JSON_LENGTH(d.payments), 0) > 0
              {$dealIdSql}
        ", array_merge($dealParams, $dealIdParams));

        $out = [];
        foreach ($rows as $dealRow) {
            $decoded = json_decode((string)($dealRow['payments_json'] ?? ''), true);
            if (!is_array($decoded)) {
                continue;
            }
            $firstCustomerPaymentDate = '';
            foreach ($decoded as $payment) {
                if (!is_array($payment) || !empty($payment['deleted']) || !empty($payment['is_deleted'])) {
                    continue;
                }
                $amount = (float)($payment['amount'] ?? 0);
                if ($amount <= 0 || $this->isCeoSudranJsonPayment($payment) || $this->isCeoJsonChequePayment($payment)) {
                    continue;
                }
                $candidateDate = $this->normalizeCeoJsonPaymentDate($payment);
                if ($candidateDate !== '' && ($firstCustomerPaymentDate === '' || $candidateDate < $firstCustomerPaymentDate)) {
                    $firstCustomerPaymentDate = $candidateDate;
                }
            }
            $sequence = 0;
            foreach ($decoded as $payment) {
                $sequence++;
                if (!is_array($payment) || !empty($payment['deleted']) || !empty($payment['is_deleted'])) {
                    continue;
                }
                $amount = (float)($payment['amount'] ?? 0);
                if ($amount <= 0) {
                    continue;
                }
                $paymentDate = $this->normalizeCeoJsonPaymentDate($payment);
                if ($paymentDate === '' || $paymentDate < $dateFrom || $paymentDate > $dateTo) {
                    continue;
                }
                $dealId = (int)($dealRow['deal_id'] ?? 0);
                $method = (string)(($payment['method'] ?? '') ?: ($payment['payment_method'] ?? ''));
                $methodCode = $this->normalizeCeoPaymentMethodCode($method);
                $registeredDate = $this->normalizeCeoJsonRegisteredDate($payment, $paymentDate);
                $isSudran = $this->isCeoSudranJsonPayment($payment);
                $isCheque = $this->isCeoJsonChequePayment($payment);
                $chequeCollectionStatus = $this->ceoJsonChequeCollectionStatus($payment);
                if ($isCheque && $chequeCollectionStatus !== 'collected') {
                    continue;
                }
                if (isset($existingKeys['seq:' . $dealId . ':' . $sequence])
                    || isset($existingKeys['fingerprint:' . $dealId . ':' . $paymentDate . ':' . $this->ceoMoneyKey($amount) . ':' . $methodCode])
                    || isset($existingKeys['legacy_amount_method:' . $dealId . ':' . $this->ceoMoneyKey($amount) . ':' . $methodCode])
                    || ($isSudran && isset($existingKeys['sudran_deal_amount:' . $dealId . ':' . $this->ceoMoneyKey($amount)]))) {
                    continue;
                }
                $baseRow = [
                    'payment_record_id' => 0,
                    'source_hash' => '',
                    'deal_id' => $dealId,
                    'deal_didar_id' => (string)($dealRow['didar_deal_id'] ?? ''),
                    'payment_uuid' => '',
                    'payment_sequence' => $sequence,
                    'source_type' => 'DEAL_JSON_FALLBACK',
                    'method' => $method,
                    'gross_amount' => $amount,
                    'refunded_amount' => (float)($payment['refund_amount'] ?? 0),
                    'refund_status' => !empty($payment['refund_amount']) ? 'PARTIAL' : 'NONE',
                    'net_amount' => $amount,
                    'payment_window_date' => $paymentDate,
                    'payment_record_payment_date' => $paymentDate,
                    'deposit_date' => $this->normalizeCeoJsonPaymentDate(['deposit_date' => $payment['deposit_date'] ?? '']) ?: '',
                    'effective_date' => $paymentDate,
                    'registered_date' => $registeredDate,
                    'registered_at' => (string)($payment['registered_at'] ?? $payment['created_at'] ?? ''),
                    'created_at' => (string)($payment['created_at'] ?? ''),
                    'first_payment_date' => $isSudran || $isCheque ? '' : $firstCustomerPaymentDate,
                    'is_cheque' => $isCheque ? 1 : 0,
                    'is_sudran' => $isSudran ? 1 : 0,
                    'deal_status' => (string)($dealRow['deal_status'] ?? ''),
                    'cheque_status' => $isCheque ? (string)($payment['cheque_status'] ?? $payment['check_status'] ?? 'pending') : 'not_cheque',
                    'cheque_collection_status' => $isCheque ? $chequeCollectionStatus : 'not_cheque',
                    'cheque_due_date' => null,
                    'cheque_collection_date' => $isCheque ? $paymentDate : null,
                    'commission_status' => '',
                ];
                if ($lean) {
                    $out[] = $baseRow;
                    continue;
                }
                $out[] = array_merge($baseRow, [
                    'transaction_id' => '',
                    'tx_payment_status' => (string)($payment['status'] ?? ''),
                    'tx_card_last4' => (string)(($payment['card_last4'] ?? '') ?: ($payment['card_last_4'] ?? '')),
                    'tx_destination_bank' => (string)(($payment['destination_bank'] ?? '') ?: ($payment['bank_destination'] ?? '')),
                    'tx_destination_card_number' => (string)($payment['destination_card_number'] ?? ''),
                    'tx_destination_bank_title' => (string)($payment['destination_bank_title'] ?? ''),
                    'tx_destination_card_full' => (string)($payment['destination_card_full'] ?? ''),
                    'tx_destination_account_number' => (string)($payment['destination_account_number'] ?? ''),
                    'tx_destination_sheba' => (string)($payment['destination_sheba'] ?? ''),
                    'tx_destination_tafsili_code' => (string)($payment['destination_tafsili_code'] ?? ''),
                    'tx_destination_account_owner' => (string)($payment['destination_account_owner'] ?? ''),
                    'tx_account_owner_name' => (string)($payment['account_owner_name'] ?? ''),
                    'tx_depositor_name' => (string)($payment['depositor_name'] ?? ''),
                    'tx_tracking_code_last4' => (string)(($payment['tracking_code_last4'] ?? '') ?: ($payment['tracking_code'] ?? '')),
                    'tx_check_owner_name' => (string)($payment['check_owner_name'] ?? ''),
                    'tx_check_bank_name' => (string)($payment['check_bank_name'] ?? ''),
                    'tx_check_number' => (string)($payment['check_number'] ?? ''),
                    'tx_check_date' => (string)($payment['check_date'] ?? ''),
                    'tx_check_due_date' => (string)($payment['check_due_date'] ?? ''),
                    'tx_sayadi_date' => (string)($payment['sayadi_date'] ?? ''),
                    'tx_check_delivery_date' => (string)($payment['check_delivery_date'] ?? ''),
                    'tx_check_recipient_name' => (string)($payment['check_recipient_name'] ?? ''),
                    'tx_receipt_image_path' => (string)($payment['receipt_image_path'] ?? ''),
                    'tx_check_image_path' => (string)($payment['check_image_path'] ?? ''),
                    'tx_registered_by_didar_id' => (string)($payment['registered_by_didar_id'] ?? ''),
                    'tx_registered_by_name' => (string)($payment['registered_by_name'] ?? ''),
                    'tx_refund_amount' => (float)($payment['refund_amount'] ?? 0),
                    'tx_refund_count' => (int)($payment['refund_count'] ?? 0),
                    'tx_refund_description' => (string)($payment['refund_description'] ?? ''),
                    'tx_description' => (string)($payment['description'] ?? ''),
                    'tx_invoice_code' => (string)($payment['invoice_code'] ?? ''),
                    'deal_registered_date' => (string)($dealRow['deal_registered_date'] ?? ''),
                    'raw_snapshot_json' => json_encode(['source' => 'deals.payments.fallback', 'payment' => $payment], JSON_UNESCAPED_UNICODE),
                    'deal_title' => (string)($dealRow['deal_title'] ?? ''),
                    'owner_didar_id' => (string)($dealRow['owner_didar_id'] ?? ''),
                    'owner_name' => (string)($dealRow['owner_name'] ?? 'بدون مسئول'),
                    'deal_created_by_didar_id' => (string)($dealRow['deal_created_by_didar_id'] ?? ''),
                    'deal_creator_role' => (string)($dealRow['deal_creator_role'] ?? ''),
                    'deal_created_via' => (string)($dealRow['deal_created_via'] ?? ''),
                    'product_name' => (string)($dealRow['product_name'] ?? 'بدون محصول'),
                    'acquaintance_source' => (string)($dealRow['acquaintance_source'] ?? 'نامشخص'),
                    'acquaintance_detail' => (string)($dealRow['acquaintance_detail'] ?? 'بدون جزئیات'),
                    'content_topic' => (string)($dealRow['content_topic'] ?? 'بدون موضوع'),
                ]);
            }
        }
        return $out;
    }

    private function isCeoSudranJsonPayment(array $payment): bool
    {
        $method = (string)(($payment['method'] ?? '') ?: ($payment['payment_method'] ?? ''));
        $description = (string)($payment['description'] ?? '');
        $code = $this->normalizeCeoPaymentMethodCode($method);
        $haystack = strtolower($method . ' ' . $description . ' ' . json_encode($payment, JSON_UNESCAPED_UNICODE));
        return in_array($code, ['sudran_payment_gateway', 'sudran_gateway', 'soodran_payment_gateway', 'soodran_gateway', 'sodran_payment_gateway'], true)
            || strpos($method . ' ' . $description, 'سودران') !== false
            || strpos($haystack, 'sudran') !== false
            || strpos($haystack, 'soodran') !== false
            || strpos($haystack, 'sodran') !== false;
    }

    private function isCeoJsonChequePayment(array $payment): bool
    {
        $method = (string)(($payment['method'] ?? '') ?: ($payment['payment_method'] ?? ''));
        $code = $this->normalizeCeoPaymentMethodCode($method);
        return in_array($code, ['check', 'cheque'], true)
            || strpos($method, 'چک') !== false
            || trim((string)($payment['check_number'] ?? '')) !== ''
            || trim((string)($payment['cheque_number'] ?? '')) !== ''
            || trim((string)($payment['check_bank_name'] ?? '')) !== ''
            || trim((string)($payment['cheque_bank_name'] ?? '')) !== ''
            || trim((string)($payment['check_due_date'] ?? '')) !== ''
            || trim((string)($payment['cheque_due_date'] ?? '')) !== '';
    }

    private function ceoJsonChequeCollectionStatus(array $payment): string
    {
        $status = strtolower(trim((string)($payment['cheque_collection_status'] ?? $payment['check_collection_status'] ?? '')));
        if (in_array($status, ['collected', 'passed', 'paid', 'cleared', 'وصول شده', 'پاس شده'], true)) {
            return 'collected';
        }
        return $status !== '' ? $status : 'not_collected';
    }

    private function normalizeCeoJsonPaymentDate(array $payment): string
    {
        $date = DateNormalizer::normalizeGregorianOrJalaliDate((string)($payment['deposit_date'] ?? ''))
            ?: DateNormalizer::normalizeGregorianOrJalaliDate((string)($payment['date'] ?? ''))
            ?: DateNormalizer::normalizeGregorianOrJalaliDate((string)($payment['payment_date'] ?? ''))
            ?: DateNormalizer::normalizeGregorianOrJalaliDate((string)($payment['date_persian'] ?? ''));
        return $date ?: '';
    }

    private function normalizeCeoJsonRegisteredDate(array $payment, string $fallbackDate): string
    {
        $registeredAt = trim((string)($payment['registered_at'] ?? ''));
        if ($registeredAt !== '' && preg_match('/^(\d{4}-\d{2}-\d{2})/', $registeredAt, $m)) {
            return DateNormalizer::normalizeGregorianOrJalaliDate($m[1]) ?: $fallbackDate;
        }
        return $fallbackDate;
    }

    private function normalizeCeoPaymentMethodCode(string $method): string
    {
        return strtolower((string)preg_replace('/[\s\-\/]+/', '_', trim($method)));
    }

    private function ceoMoneyKey(float $amount): string
    {
        return number_format($amount, 2, '.', '');
    }

    private function classifyCeoPaymentRow(array $row, array $filters): string
    {
        $paymentDate = trim((string)($row['payment_window_date'] ?? ''));
        $firstDate = trim((string)($row['first_payment_date'] ?? ''));
        if ($paymentDate === '') {
            return '';
        }
        $isCheque = (int)($row['is_cheque'] ?? 0) === 1;
        $isSudran = (int)($row['is_sudran'] ?? 0) === 1;
        $dealStatus = (string)($row['deal_status'] ?? '');
        if ($dealStatus === 'Lost') {
            return 'lost_deposit';
        }
        if ($isSudran) {
            return 'sudran_deposit';
        }
        if ($isCheque) {
            return ((string)($row['cheque_collection_status'] ?? '') === 'collected') ? 'cheque_collected' : '';
        }
        if ($firstDate !== '' && $paymentDate === $firstDate) {
            return 'sales_deposit';
        }
        return 'balance_deposit';
    }

    private function ceoLeadSourceBucket(array $row, string $category): string
    {
        if ($category === 'sudran_deposit' || (int)($row['is_sudran'] ?? 0) === 1) {
            return 'sudran';
        }
        $creatorRole = strtolower(trim((string)($row['deal_creator_role'] ?? '')));
        return $creatorRole === 'agent' ? 'personal' : 'crm';
    }

    private function getCeoAllTimeCustomerPaymentTotalsByDeal(array $filters, array $dealIds = []): array
    {
        if (!$this->tableHasColumns('payment_records', ['id', 'deal_id', 'amount'])) {
            return [];
        }

        $dealIds = array_values(array_unique(array_filter(array_map('intval', $dealIds))));
        $wanted = !empty($dealIds) ? array_fill_keys($dealIds, true) : [];
        $facts = $this->getCeoAllTimeNormalizedCustomerPaymentFactsByDeal($filters, $dealIds);
        $totals = [];
        foreach ($facts as $dealId => $row) {
            $dealId = (int)$dealId;
            if ($dealId <= 0) {
                continue;
            }
            if (!empty($wanted) && !isset($wanted[$dealId])) {
                continue;
            }
            $totals[$dealId] = (float)($row['total_paid'] ?? 0);
        }
        return $totals;
    }

    private function getCeoRegisteredPaymentCount(array $filters): int
    {
        [$dateFrom, $dateTo] = $this->dateRangeFromFilters($filters);
        $dealWhere = $this->buildWhereClauseForDealsNoDate($filters, 'd');
        $dealParams = $this->buildParamsForDealsNoDate($filters);
        $registeredDate = $this->ceoPaymentRegisteredDateExpr('pr');
        $valid = $this->ceoPaymentValidWhere('pr');
        $row = $this->runSelectAll("
            SELECT COUNT(*) as total
            FROM payment_records pr
            INNER JOIN deals d ON d.id = pr.deal_id
            WHERE {$valid}
              AND {$registeredDate} BETWEEN ? AND ?
              AND {$dealWhere}
        ", array_merge([$dateFrom, $dateTo], $dealParams));
        return (int)($row[0]['total'] ?? 0);
    }

    private function getCeoRegisteredDealsBySource(array $filters): array
    {
        $whereDeals = $this->buildWhereClauseForDeals($filters, 'd');
        $params = $this->buildParamsForDeals($filters);
        $paymentDate = $this->ceoPaymentDateExpr('pr');
        $isCheque = $this->ceoPaymentIsChequeExpr('pr');
        $valid = $this->ceoPaymentValidWhere('pr');
        $sql = "
            SELECT
                COALESCE(NULLIF(TRIM(d.acquaintance_source), ''), 'نامشخص') as acquaintance_source,
                COALESCE(NULLIF(TRIM(d.acquaintance_detail), ''), 'بدون جزئیات') as acquaintance_detail,
                COALESCE(NULLIF(TRIM(d.content_topic), ''), 'بدون موضوع') as content_topic,
                COUNT(DISTINCT d.id) as deal_count,
                SUM(COALESCE(d.payable_amount, 0)) as payable_amount,
                SUM(CASE WHEN fp.first_payment_date BETWEEN ? AND ? THEN 1 ELSE 0 END) as first_payment_deal_count
            FROM deals d
            LEFT JOIN (
                SELECT pr.deal_id, MIN({$paymentDate}) as first_payment_date
                FROM payment_records pr
                WHERE {$valid}
                  AND ({$isCheque} = 0 OR pr.cheque_collection_status = 'collected')
                  AND {$paymentDate} IS NOT NULL
                GROUP BY pr.deal_id
            ) fp ON fp.deal_id = d.id
            WHERE {$whereDeals}
            GROUP BY acquaintance_source, acquaintance_detail, content_topic
            ORDER BY deal_count DESC, payable_amount DESC
            LIMIT 100
        ";
        [$dateFrom, $dateTo] = $this->dateRangeFromFilters($filters);
        return $this->runSelectAll($sql, array_merge([$dateFrom, $dateTo], $params));
    }

    private function getCeoAccessGrantRows(array $filters): array
    {
        if (!$this->tableHasColumns('customer_access_requests', ['id', 'status', 'created_at'])) {
            return [];
        }
        [$dateFrom, $dateTo] = $this->dateRangeFromFilters($filters);
        $dealWhere = $this->buildWhereClauseForDealsNoDate($filters, 'd');
        $dealParams = $this->buildParamsForDealsNoDate($filters);
        $dateExpr = "DATE(COALESCE(CASE WHEN car.status = 'done' THEN car.resolved_at ELSE NULL END, car.created_at))";
        $sql = "
            SELECT
                COALESCE(car.status, 'pending') as status,
                COUNT(*) as count,
                SUM(CASE WHEN car.status = 'done' THEN 1 ELSE 0 END) as done_count
            FROM customer_access_requests car
            LEFT JOIN deals d ON d.id = car.deal_id
            WHERE {$dateExpr} BETWEEN ? AND ?
              AND (car.deal_id IS NULL OR {$dealWhere})
            GROUP BY COALESCE(car.status, 'pending')
            ORDER BY count DESC
        ";
        return $this->runSelectAll($sql, array_merge([$dateFrom, $dateTo], $dealParams));
    }

    private function getCeoChequeStatusRows(array $filters): array
    {
        [$dateFrom, $dateTo] = $this->dateRangeFromFilters($filters);
        $dealWhere = $this->buildWhereClauseForDealsNoDate($filters, 'd');
        $dealParams = $this->buildParamsForDealsNoDate($filters);
        $isCheque = $this->ceoPaymentIsChequeExpr('pr');
        $valid = $this->ceoPaymentValidWhere('pr');
        $netAmount = $this->ceoPaymentNetAmountExpr('pr');
        $chequeWindowDate = "DATE(COALESCE(pr.cheque_collection_date, pr.cheque_due_date, pr.effective_date, pr.payment_date, pr.created_at))";
        $baseWhere = "{$valid}
            AND {$isCheque}
            AND {$chequeWindowDate} BETWEEN ? AND ?
            AND {$dealWhere}";

        $params = array_merge([$dateFrom, $dateTo], $dealParams);
        $byStatus = $this->runSelectAll("
            SELECT
                COALESCE(pr.cheque_status, 'pending') as status,
                COUNT(*) as count,
                COUNT(DISTINCT pr.deal_id) as deal_count,
                SUM({$netAmount}) as amount
            FROM payment_records pr
            INNER JOIN deals d ON d.id = pr.deal_id
            WHERE {$baseWhere}
            GROUP BY COALESCE(pr.cheque_status, 'pending')
            ORDER BY count DESC
        ", $params);

        $byCollection = $this->runSelectAll("
            SELECT
                COALESCE(pr.cheque_collection_status, 'pending') as status,
                COUNT(*) as count,
                COUNT(DISTINCT pr.deal_id) as deal_count,
                SUM({$netAmount}) as amount
            FROM payment_records pr
            INNER JOIN deals d ON d.id = pr.deal_id
            WHERE {$baseWhere}
            GROUP BY COALESCE(pr.cheque_collection_status, 'pending')
            ORDER BY count DESC
        ", $params);

        return [
            'by_status' => $byStatus,
            'by_collection_status' => $byCollection,
        ];
    }

    private function getCeoPerformanceDrilldown(array $filters, string $metricKey, array $rowContext): array
    {
        if (!$this->tableHasColumns('payment_records', ['id', 'deal_id', 'amount'])) {
            return $this->emptyDrilldownPayload();
        }

        if ($metricKey === 'registered_deals_count' || $metricKey === 'marketing_source' || $metricKey === 'registered_deals_source') {
            return $this->getCeoMarketingDrilldown($filters, $rowContext);
        }

        if ($metricKey === 'access_granted_count' || $metricKey === 'access_status') {
            return $this->getCeoAccessDrilldown($filters, $rowContext);
        }

        if ($metricKey === 'cheque_status' || $metricKey === 'cheque_collection_status' || $metricKey === 'all_cheques_count' || $metricKey === 'cheque_state') {
            return $this->getCeoChequeStatusDrilldown($filters, $metricKey, $rowContext);
        }

        if ($metricKey === 'products_sold_count' || $metricKey === 'product_sales') {
            return $this->getCeoProductSalesDrilldown($filters, $rowContext);
        }

        if ($metricKey === 'agent_sales') {
            return $this->getCeoAgentSalesDrilldown($filters, $rowContext);
        }

        if ($metricKey === 'outstanding_balance_total' || $metricKey === 'outstanding_balance_month') {
            return $this->getCeoOutstandingBalanceDrilldown($filters, $rowContext);
        }

        if ($metricKey === 'deal_status') {
            return $this->getCeoDealStatusDrilldown($filters, $rowContext);
        }

        $depositCohortMetricMap = [
            'deposit_registered_in_range_amount' => 'payment_registered_in_range_amount',
            'deposit_registered_in_range_receipt_count' => 'payment_registered_in_range_amount',
            'deposit_outside_registered_first_payment_amount' => 'payment_outside_registered_first_payment_amount',
            'deposit_outside_registered_first_payment_receipt_count' => 'payment_outside_registered_first_payment_amount',
        ];
        if (isset($depositCohortMetricMap[$metricKey])) {
            return $this->getCeoPaymentDealRangeDrilldown($filters, $metricKey);
        }

        $categoryMap = [
            'total_deposits_amount' => '',
            'total_deposits_receipt_count' => '',
            'sales_deposit_amount' => 'sales_deposit',
            'sales_deposit_receipt_count' => 'sales_deposit',
            'balance_deposit_amount' => 'balance_deposit',
            'balance_deposit_receipt_count' => 'balance_deposit',
            'first_payment_amount' => 'sales_deposit',
            'first_payment_receipt_count' => 'sales_deposit',
            'balance_current_amount' => 'balance_deposit',
            'balance_current_receipt_count' => 'balance_deposit',
            'balance_previous_amount' => 'balance_deposit',
            'balance_previous_receipt_count' => 'balance_deposit',
            'cheque_collected_amount' => 'cheque_collected',
            'cheque_collected_count' => 'cheque_collected',
            'sudran_deposit_amount' => 'sudran_deposit',
            'sudran_deposit_receipt_count' => 'sudran_deposit',
            'lost_deposit_amount' => 'lost_deposit',
            'lost_deposit_receipt_count' => 'lost_deposit',
            'cash_category' => (string)($rowContext['category'] ?? ''),
        ];

        if (array_key_exists($metricKey, $categoryMap)) {
            $category = trim((string)$categoryMap[$metricKey]);
            $rows = [];
            foreach ($this->getCeoPaymentRows($filters) as $row) {
                $rowCategory = $this->classifyCeoPaymentRow($row, $filters);
                if ($rowCategory === '') {
                    continue;
                }
                if ($category !== '' && $rowCategory !== $category) {
                    continue;
                }
                $rows[] = $row;
            }
            return $this->buildCeoPaymentDrilldown($rows, $filters);
        }

        return $this->emptyDrilldownPayload();
    }

    private function emptyDrilldownPayload(): array
    {
        return ['leads' => [], 'deals' => [], 'activities' => [], 'payments' => [], 'meta' => ['total_leads' => 0, 'total_deals' => 0, 'total_activities' => 0, 'total_payments' => 0]];
    }

    private function getCeoPaymentDealRangeDrilldown(array $filters, string $metricKey): array
    {
        $wantInside = in_array($metricKey, ['deposit_registered_in_range_amount', 'deposit_registered_in_range_receipt_count'], true);
        $rows = [];
        foreach ($this->getCeoPaymentRows($filters) as $row) {
            if ($this->classifyCeoPaymentRow($row, $filters) === '') {
                continue;
            }
            $inside = $this->ceoDateInFilterRange((string)($row['deal_registered_date'] ?? ''), $filters);
            if ($inside === $wantInside) {
                $rows[] = $row;
            }
        }
        return $this->buildCeoPaymentDrilldown($rows, $filters);
    }

    private function getCeoRegisteredPaymentRows(array $filters): array
    {
        [$dateFrom, $dateTo] = $this->dateRangeFromFilters($filters);
        $dealWhere = $this->buildWhereClauseForDealsNoDate($filters, 'd');
        $dealParams = $this->buildParamsForDealsNoDate($filters);
        $registeredDate = $this->ceoPaymentRegisteredDateExpr('pr');
        $netAmount = $this->ceoPaymentNetAmountExpr('pr');
        $isCheque = $this->ceoPaymentIsChequeExpr('pr');
        $valid = $this->ceoPaymentValidWhere('pr');

        return $this->runSelectAll("
            SELECT
                pr.id as payment_record_id,
                pr.deal_id,
                pr.deal_didar_id,
                pr.payment_uuid,
                pr.source_hash,
                pr.payment_sequence,
                pr.source_type,
                pr.method,
                pr.amount as gross_amount,
                pr.refunded_amount,
                pr.refund_status,
                pr.payment_date as payment_record_payment_date,
                pr.deposit_date,
                pr.effective_date,
                pr.registered_at,
                pr.created_at,
                pr.payment_owner_user_id,
                pr.payment_owner_didar_id,
                pr.payment_owner_name_snapshot,
                pr.payment_owner_source,
                pr.finance_status,
                pr.sepidar_registered_at,
                pr.commission_status,
                pr.raw_snapshot_json,
                {$netAmount} as net_amount,
                {$this->ceoPaymentDateExpr('pr')} as payment_window_date,
                {$registeredDate} as registered_date,
                CASE WHEN {$isCheque} THEN 1 ELSE 0 END as is_cheque,
                COALESCE(pr.cheque_status, 'not_cheque') as cheque_status,
                COALESCE(pr.cheque_collection_status, 'not_cheque') as cheque_collection_status,
                pr.cheque_due_date,
                pr.cheque_collection_date,
                tx.id as transaction_id,
                COALESCE(tx.status, '') as tx_payment_status,
                COALESCE(tx.card_last4, '') as tx_card_last4,
                COALESCE(tx.destination_bank, '') as tx_destination_bank,
                COALESCE(tx.destination_card_number, '') as tx_destination_card_number,
                COALESCE(tx.destination_bank_title, '') as tx_destination_bank_title,
                COALESCE(tx.destination_card_full, '') as tx_destination_card_full,
                COALESCE(tx.destination_account_number, '') as tx_destination_account_number,
                COALESCE(tx.destination_sheba, '') as tx_destination_sheba,
                COALESCE(tx.destination_tafsili_code, '') as tx_destination_tafsili_code,
                COALESCE(tx.destination_account_owner, '') as tx_destination_account_owner,
                COALESCE(tx.account_owner_name, '') as tx_account_owner_name,
                COALESCE(tx.depositor_name, '') as tx_depositor_name,
                COALESCE(tx.tracking_code_last4, '') as tx_tracking_code_last4,
                COALESCE(tx.check_owner_name, '') as tx_check_owner_name,
                COALESCE(tx.check_bank_name, '') as tx_check_bank_name,
                COALESCE(tx.check_number, '') as tx_check_number,
                tx.check_date as tx_check_date,
                tx.check_due_date as tx_check_due_date,
                COALESCE(tx.sayadi_date, '') as tx_sayadi_date,
                tx.check_delivery_date as tx_check_delivery_date,
                COALESCE(tx.check_recipient_name, '') as tx_check_recipient_name,
                COALESCE(tx.receipt_image_path, '') as tx_receipt_image_path,
                COALESCE(tx.check_image_path, '') as tx_check_image_path,
                COALESCE(tx.registered_by_didar_id, '') as tx_registered_by_didar_id,
                COALESCE(tx.registered_by_name, '') as tx_registered_by_name,
                COALESCE(tx.refund_amount, 0) as tx_refund_amount,
                COALESCE(tx.refund_count, 0) as tx_refund_count,
                COALESCE(tx.refund_description, '') as tx_refund_description,
                COALESCE(tx.description, '') as tx_description,
                COALESCE(tx.invoice_code, '') as tx_invoice_code,
                COALESCE(d.title, '') as deal_title,
                COALESCE(d.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name,
                {$this->buildResolvedProductNameExpr($this->buildDealProductHintExpr('d'), 'p', 'dp')} as product_name
            FROM payment_records pr
            INNER JOIN deals d ON d.id = pr.deal_id
            LEFT JOIN ({$this->ceoPreferredTransactionLinkSubquery()}) prl ON prl.payment_record_id = pr.id
            LEFT JOIN transactions tx ON tx.id = prl.transaction_id
            LEFT JOIN users u ON u.didar_user_id = d.owner_didar_id
            LEFT JOIN ({$this->buildPrimaryDealProductSql()}) dp ON dp.deal_id = d.id
            LEFT JOIN products p ON p.id = dp.product_id
            WHERE {$valid}
              AND {$registeredDate} BETWEEN ? AND ?
              AND {$dealWhere}
            ORDER BY {$registeredDate} DESC, pr.id DESC
        ", array_merge([$dateFrom, $dateTo], $dealParams));
    }

    private function buildCeoPaymentDrilldown(array $paymentRows, array $filters): array
    {
        $dealIds = [];
        $payments = [];
        foreach ($paymentRows as $row) {
            $dealId = (int)($row['deal_id'] ?? 0);
            if ($dealId > 0) {
                $dealIds[$dealId] = $dealId;
            }
            $snapshot = $this->extractCeoPaymentSnapshot($row);
            $pick = function (array $rowKeys, array $snapshotKeys = [], $default = '') use ($row, $snapshot) {
                foreach ($rowKeys as $key) {
                    if (array_key_exists($key, $row) && $row[$key] !== null && $row[$key] !== '') {
                        return $row[$key];
                    }
                }
                foreach ($snapshotKeys as $key) {
                    if (array_key_exists($key, $snapshot) && $snapshot[$key] !== null && $snapshot[$key] !== '') {
                        return $snapshot[$key];
                    }
                }
                return $default;
            };
            $method = (string)$pick(['method'], ['method', 'payment_method']);
            $refundAmount = (float)$pick(['tx_refund_amount', 'refunded_amount'], ['refund_amount'], 0);
            $paymentStatus = (string)$pick(['tx_payment_status', 'commission_status'], ['status']);
            $sourceType = strtoupper(trim((string)($row['source_type'] ?? '')));
            if ($sourceType === 'DEAL_JSON' && in_array(strtolower(trim($paymentStatus)), ['cancelled', 'canceled', 'deleted', 'void'], true)) {
                $paymentStatus = (string)$pick([], ['status'], '');
                if ($paymentStatus === '' || in_array(strtolower(trim($paymentStatus)), ['cancelled', 'canceled', 'deleted', 'void'], true)) {
                    $paymentStatus = 'confirmed';
                }
            }
            $payments[] = [
                'payment_record_id' => $row['payment_record_id'] ?? '',
                'source_hash' => $row['source_hash'] ?? '',
                'payment_uuid' => $row['payment_uuid'] ?? '',
                'payment_sequence' => $row['payment_sequence'] ?? '',
                'transaction_id' => $row['transaction_id'] ?? '',
                'deal_id' => $dealId,
                'didar_deal_id' => $row['deal_didar_id'] ?? '',
                'deal_title' => $row['deal_title'] ?? '',
                'owner_name' => $row['owner_name'] ?? '',
                'owner_didar_id' => $row['owner_didar_id'] ?? '',
                'product_name' => $row['product_name'] ?? '',
                'payment_date' => $row['payment_window_date'] ?? ($row['registered_date'] ?? ''),
                'payment_record_payment_date' => $row['payment_record_payment_date'] ?? '',
                'deposit_date' => $row['deposit_date'] ?? '',
                'effective_date' => $row['effective_date'] ?? '',
                'registered_date' => $row['registered_date'] ?? '',
                'registered_at' => $row['registered_at'] ?? '',
                'created_at' => $row['created_at'] ?? '',
                'amount' => (float)($row['net_amount'] ?? 0),
                'gross_amount' => (float)($row['gross_amount'] ?? $row['net_amount'] ?? 0),
                'refunded_amount' => (float)($row['refunded_amount'] ?? 0),
                'refund_amount' => $refundAmount,
                'refund_count' => (int)$pick(['tx_refund_count'], ['refund_count'], 0),
                'refund_status' => (string)$pick(['refund_status'], ['refund_status']),
                'refund_description' => (string)$pick(['tx_refund_description'], ['refund_description']),
                'payment_method' => $method,
                'method' => $method,
                'payment_type' => $method,
                'payment_status' => $paymentStatus,
                'payment_source' => $row['source_type'] ?? 'payment_records',
                'payment_owner_user_id' => $row['payment_owner_user_id'] ?? '',
                'payment_owner_didar_id' => $row['payment_owner_didar_id'] ?? '',
                'payment_owner_name' => $row['payment_owner_name_snapshot'] ?? '',
                'payment_owner_source' => $row['payment_owner_source'] ?? '',
                'registered_by_name' => (string)$pick(['tx_registered_by_name'], ['registered_by_name'], $row['payment_owner_name_snapshot'] ?? ''),
                'registered_by_didar_id' => (string)$pick(['tx_registered_by_didar_id'], ['registered_by_didar_id'], $row['payment_owner_didar_id'] ?? ''),
                'finance_status' => $row['finance_status'] ?? '',
                'sepidar_registered_at' => $row['sepidar_registered_at'] ?? '',
                'commission_status' => $row['commission_status'] ?? '',
                'cheque_status' => $row['cheque_status'] ?? '',
                'cheque_collection_status' => $row['cheque_collection_status'] ?? '',
                'cheque_due_date' => $row['cheque_due_date'] ?? '',
                'cheque_collection_date' => $row['cheque_collection_date'] ?? '',
                'card_last4' => (string)$pick(['tx_card_last4'], ['card_last4', 'card_last_4']),
                'destination_bank' => (string)$pick(['tx_destination_bank'], ['destination_bank', 'bank_destination']),
                'destination_bank_title' => (string)$pick(['tx_destination_bank_title'], ['destination_bank_title']),
                'destination_card_number' => (string)$pick(['tx_destination_card_number'], ['destination_card_number']),
                'destination_card_full' => (string)$pick(['tx_destination_card_full'], ['destination_card_full']),
                'destination_account_number' => (string)$pick(['tx_destination_account_number'], ['destination_account_number']),
                'destination_sheba' => (string)$pick(['tx_destination_sheba'], ['destination_sheba']),
                'destination_tafsili_code' => (string)$pick(['tx_destination_tafsili_code'], ['destination_tafsili_code']),
                'destination_account_owner' => (string)$pick(['tx_destination_account_owner'], ['destination_account_owner']),
                'account_owner_name' => (string)$pick(['tx_account_owner_name'], ['account_owner_name']),
                'depositor_name' => (string)$pick(['tx_depositor_name'], ['depositor_name']),
                'tracking_code_last4' => (string)$pick(['tx_tracking_code_last4'], ['tracking_code_last4', 'tracking_code']),
                'card_reader_device' => (string)$pick([], ['card_reader_device']),
                'currency' => (string)$pick([], ['payment_currency', 'currency']),
                'currency_other' => (string)$pick([], ['payment_currency_other', 'currency_other']),
                'currency_amount' => (float)$pick([], ['payment_currency_amount', 'currency_amount'], 0),
                'check_bank_name' => (string)$pick(['tx_check_bank_name'], ['check_bank_name']),
                'check_date' => (string)$pick(['tx_check_date'], ['check_date']),
                'check_due_date' => (string)$pick(['tx_check_due_date'], ['check_due_date']),
                'check_number' => (string)$pick(['tx_check_number'], ['check_number']),
                'check_owner_name' => (string)$pick(['tx_check_owner_name'], ['check_owner_name']),
                'sayadi_date' => (string)$pick(['tx_sayadi_date'], ['sayadi_date']),
                'check_delivery_date' => (string)$pick(['tx_check_delivery_date'], ['check_delivery_date']),
                'check_recipient_name' => (string)$pick(['tx_check_recipient_name'], ['check_recipient_name']),
                'receipt_image_path' => (string)$pick(['tx_receipt_image_path'], ['receipt_image_path']),
                'check_image_path' => (string)$pick(['tx_check_image_path'], ['check_image_path']),
                'description' => (string)$pick(['tx_description'], ['description']),
                'invoice_code' => (string)$pick(['tx_invoice_code'], ['invoice_code']),
            ];
        }

        $base = $this->buildSimpleDealLeadDrilldownByIds(array_values($dealIds), $filters);

        // Attach lead name + contact id to each payment row so the drill-down's
        // single "lead" column can render the lead name as a link. Reuses the
        // deals/leads already fetched above, no extra query.
        $dealContactMap = [];
        foreach (($base['deals'] ?? []) as $dealRow) {
            $dealContactMap[(int)($dealRow['deal_id'] ?? 0)] = (string)($dealRow['contact_didar_id'] ?? '');
        }
        $leadNameMap = [];
        foreach (($base['leads'] ?? []) as $leadRow) {
            $leadContactId = (string)($leadRow['contact_id'] ?? '');
            if ($leadContactId !== '') {
                $leadNameMap[$leadContactId] = (string)($leadRow['full_name'] ?? '');
            }
        }
        foreach ($payments as &$paymentRow) {
            $contactId = $dealContactMap[(int)($paymentRow['deal_id'] ?? 0)] ?? '';
            $paymentRow['contact_didar_id'] = $contactId;
            $paymentRow['contact_name'] = ($contactId !== '' && isset($leadNameMap[$contactId])) ? $leadNameMap[$contactId] : '';
        }
        unset($paymentRow);

        $base['payments'] = $payments;
        $base['meta']['total_payments'] = count($payments);
        $base['meta']['total_deals'] = count($base['deals'] ?? []);
        return $base;
    }

    private function extractCeoPaymentSnapshot(array $row): array
    {
        $raw = (string)($row['raw_snapshot_json'] ?? '');
        if ($raw === '') {
            return [];
        }
        $decoded = json_decode($raw, true);
        if (!is_array($decoded)) {
            return [];
        }
        if (isset($decoded['payment']) && is_array($decoded['payment'])) {
            return $decoded['payment'];
        }
        return $decoded;
    }

    private function getCeoProductSalesDrilldown(array $filters, array $rowContext): array
    {
        $targetProduct = trim((string)($rowContext['product_name'] ?? ''));
        $allPaymentRows = [];
        $productDealIds = [];
        foreach ($this->getCeoPaymentRows($filters) as $row) {
            $category = $this->classifyCeoPaymentRow($row, $filters);
            if ($category === '') {
                continue;
            }
            $dealId = (int)($row['deal_id'] ?? 0);
            if ($dealId <= 0) {
                continue;
            }
            $allPaymentRows[] = $row;
            if ($category === 'sales_deposit' || $category === 'sudran_deposit') {
                $productDealIds[$dealId] = $dealId;
            }
        }
        $paymentRows = array_values(array_filter($allPaymentRows, static function ($row) use ($productDealIds) {
            $dealId = (int)($row['deal_id'] ?? 0);
            return $dealId > 0 && isset($productDealIds[$dealId]);
        }));
        if ($targetProduct === '') {
            return $this->buildCeoPaymentDrilldown($paymentRows, $filters);
        }

        $dealIds = [];
        foreach ($productDealIds as $dealId) {
            $dealIds[$dealId] = $dealId;
        }
        $matchedDeals = [];
        if (!empty($dealIds)) {
            $placeholders = implode(',', array_fill(0, count($dealIds), '?'));
            $rows = $this->runSelectAll("
                SELECT DISTINCT dp.deal_id
                FROM deal_products dp
                LEFT JOIN products p ON p.id = dp.product_id
                WHERE dp.deal_id IN ({$placeholders})
                  AND COALESCE(NULLIF(TRIM(dp.service_title_snapshot), ''), NULLIF(TRIM(dp.product_name_snapshot), ''), NULLIF(TRIM(p.name), ''), 'نامشخص') = ?
            ", array_merge(array_values($dealIds), [$targetProduct]));
            foreach ($rows as $row) {
                $matchedDeals[(int)($row['deal_id'] ?? 0)] = true;
            }
        }

        $rows = [];
        foreach ($paymentRows as $row) {
            $dealId = (int)($row['deal_id'] ?? 0);
            $fallbackMatch = trim((string)($row['product_name'] ?? '')) === $targetProduct;
            if (($dealId > 0 && isset($matchedDeals[$dealId])) || $fallbackMatch) {
                $rows[] = $row;
            }
        }

        return $this->buildCeoPaymentDrilldown($rows, $filters);
    }

    private function getCeoMarketingDrilldown(array $filters, array $rowContext): array
    {
        $source = trim((string)($rowContext['acquaintance_source'] ?? ''));
        $detail = trim((string)($rowContext['acquaintance_detail'] ?? ''));
        $topic = trim((string)($rowContext['content_topic'] ?? ''));
        $rows = [];
        foreach ($this->getCeoPaymentRows($filters) as $row) {
            $rowSource = trim((string)($row['acquaintance_source'] ?? '')) ?: 'نامشخص';
            $rowDetail = trim((string)($row['acquaintance_detail'] ?? '')) ?: 'بدون جزئیات';
            $rowTopic = trim((string)($row['content_topic'] ?? '')) ?: 'بدون موضوع';
            if ($source !== '' && $rowSource !== $source) {
                continue;
            }
            if ($detail !== '' && $rowDetail !== $detail) {
                continue;
            }
            if ($topic !== '' && $rowTopic !== $topic) {
                continue;
            }
            $rows[] = $row;
        }
        return $this->buildCeoPaymentDrilldown($rows, $filters);
    }

    private function getCeoAgentSalesDrilldown(array $filters, array $rowContext): array
    {
        $ownerDidarId = trim((string)($rowContext['owner_didar_id'] ?? ''));
        $ownerName = trim((string)($rowContext['owner_name'] ?? ''));
        $rows = [];
        foreach ($this->getCeoPaymentRows($filters) as $row) {
            $rowCategory = $this->classifyCeoPaymentRow($row, $filters);
            if ($rowCategory === '') {
                continue;
            }
            $rowOwnerDidarId = trim((string)($row['owner_didar_id'] ?? ''));
            $rowOwnerName = trim((string)($row['owner_name'] ?? ''));
            if ($ownerDidarId !== '') {
                if ($rowOwnerDidarId !== $ownerDidarId) {
                    continue;
                }
            } elseif ($ownerName !== '' && $rowOwnerName !== $ownerName) {
                continue;
            }
            $rows[] = $row;
        }
        return $this->buildCeoPaymentDrilldown($rows, $filters);
    }

    private function getCeoRegisteredDealsDrilldown(array $filters, array $rowContext): array
    {
        $whereDeals = $this->buildWhereClauseForDeals($filters, 'd');
        $params = $this->buildParamsForDeals($filters);
        $source = trim((string)($rowContext['acquaintance_source'] ?? ''));
        $detail = trim((string)($rowContext['acquaintance_detail'] ?? ''));
        $topic = trim((string)($rowContext['content_topic'] ?? ''));

        $extra = [];
        if ($source !== '') {
            $extra[] = "COALESCE(NULLIF(TRIM(d.acquaintance_source), ''), 'نامشخص') = ?";
            $params[] = $source;
        }
        if ($detail !== '') {
            $extra[] = "COALESCE(NULLIF(TRIM(d.acquaintance_detail), ''), 'بدون جزئیات') = ?";
            $params[] = $detail;
        }
        if ($topic !== '') {
            $extra[] = "COALESCE(NULLIF(TRIM(d.content_topic), ''), 'بدون موضوع') = ?";
            $params[] = $topic;
        }
        $extraSql = !empty($extra) ? ' AND ' . implode(' AND ', $extra) : '';
        $rows = $this->runSelectAll("
            SELECT d.id
            FROM deals d
            WHERE {$whereDeals}
              {$extraSql}
            ORDER BY DATE(COALESCE(d.register_time, d.created_at)) DESC, d.id DESC
        ", $params);
        return $this->buildSimpleDealLeadDrilldownByIds(array_map('intval', array_column($rows, 'id')), $filters);
    }

    private function getCeoAccessDrilldown(array $filters, array $rowContext): array
    {
        if (!$this->tableHasColumns('customer_access_requests', ['id', 'status', 'created_at'])) {
            return $this->emptyDrilldownPayload();
        }
        [$dateFrom, $dateTo] = $this->dateRangeFromFilters($filters);
        $status = trim((string)($rowContext['status'] ?? ''));
        $dealWhere = $this->buildWhereClauseForDealsNoDate($filters, 'd');
        $dealParams = $this->buildParamsForDealsNoDate($filters);
        $dateExpr = "DATE(COALESCE(CASE WHEN car.status = 'done' THEN car.resolved_at ELSE NULL END, car.created_at))";
        $params = [$dateFrom, $dateTo];
        $statusSql = '';
        if ($status !== '') {
            $statusSql = " AND COALESCE(car.status, 'pending') = ?";
            $params[] = $status;
        }
        $rows = $this->runSelectAll("
            SELECT car.deal_id
            FROM customer_access_requests car
            LEFT JOIN deals d ON d.id = car.deal_id
            WHERE {$dateExpr} BETWEEN ? AND ?
              {$statusSql}
              AND car.deal_id IS NOT NULL
              AND {$dealWhere}
            GROUP BY car.deal_id
            ORDER BY MAX(car.id) DESC
        ", array_merge($params, $dealParams));
        return $this->buildSimpleDealLeadDrilldownByIds(array_map('intval', array_column($rows, 'deal_id')), $filters);
    }

    private function getCeoOutstandingBalanceDrilldown(array $filters, array $rowContext): array
    {
        $targetMonth = trim((string)($rowContext['category'] ?? ''));
        if ($targetMonth === '') {
            $targetMonth = trim((string)($rowContext['key'] ?? ''));
        }
        if (in_array($targetMonth, ['outstanding_balance_total', 'outstanding_total'], true)) {
            $targetMonth = '';
        }
        $visibleMonthStartKey = $this->ceoOutstandingVisibleMonthStartKey($filters);
        $rows = $this->getCeoOutstandingBalanceDealRows($filters);

        $ids = [];
        $factsByDeal = [];
        foreach ($rows as $row) {
            if ((float)($row['outstanding_amount'] ?? 0) <= 0) {
                continue;
            }
            $month = $this->ceoJalaliMonthLabel((string)($row['first_payment_date'] ?? ''));
            if ((string)($month['sort_key'] ?? '') < $visibleMonthStartKey) {
                continue;
            }
            if ($targetMonth !== '' && $month['key'] !== $targetMonth) {
                continue;
            }
            $dealId = (int)($row['deal_id'] ?? 0);
            if ($dealId <= 0) {
                continue;
            }
            $ids[] = $dealId;
            $factsByDeal[$dealId] = [
                'first_payment_date' => (string)($row['first_payment_date'] ?? ''),
                'outstanding_month_key' => (string)($month['key'] ?? ''),
                'outstanding_month_label' => (string)($month['label'] ?? ''),
                'payable_amount' => (float)($row['payable_amount'] ?? 0),
                'total_paid' => (float)($row['total_paid'] ?? 0),
                'payment_amount' => (float)($row['total_paid'] ?? 0),
                'payments_total_amount' => (float)($row['total_paid'] ?? 0),
                'outstanding_amount' => (float)($row['outstanding_amount'] ?? 0),
                'outstanding_balance' => (float)($row['outstanding_amount'] ?? 0),
            ];
        }
        $result = $this->buildSimpleDealLeadDrilldownByIds($ids, $filters);
        foreach ($result['deals'] as &$dealRow) {
            $dealId = (int)($dealRow['deal_id'] ?? 0);
            if ($dealId > 0 && isset($factsByDeal[$dealId])) {
                $dealRow = array_merge($dealRow, $factsByDeal[$dealId]);
            }
        }
        unset($dealRow);
        $result['meta']['drilldown_kind'] = 'outstanding_balance';
        $result['meta']['row_type'] = 'deal';
        $result['meta']['title'] = 'مانده حساب معاملات';
        $result['meta']['total_outstanding_balance'] = array_sum(array_map(static function ($row) {
            return (float)($row['outstanding_balance'] ?? 0);
        }, $factsByDeal));
        return $result;
    }

    private function getCeoDealStatusDrilldown(array $filters, array $rowContext): array
    {
        $target = trim((string)($rowContext['key'] ?? $rowContext['category'] ?? ''));
        [$dateFrom, $dateTo] = $this->dateRangeFromFilters($filters);
        $whereDeals = $this->buildWhereClauseForDealsNoDate($filters, 'd');
        $params = $this->buildParamsForDealsNoDate($filters);
        $paymentDate = $this->ceoPaymentDateExpr('pr');
        $netAmount = $this->ceoPaymentNetAmountExpr('pr');
        $isCheque = $this->ceoPaymentIsChequeExpr('pr');
        $isSudran = $this->ceoPaymentIsSudranExpr('pr');
        $valid = $this->ceoPaymentValidWhere('pr');
        $paymentOverlayByDeal = $this->buildCeoPaymentOverlayByDeal($filters);
        $noAnswerExpr = "(LOWER(CONCAT_WS(' ', COALESCE(a.activity_type_title, ''), COALESCE(a.title, ''), COALESCE(a.note, ''), COALESCE(a.result_note, ''), COALESCE(a.call_type, ''))) LIKE '%no answer%'
            OR CONCAT_WS(' ', COALESCE(a.activity_type_title, ''), COALESCE(a.title, ''), COALESCE(a.note, ''), COALESCE(a.result_note, ''), COALESCE(a.call_type, '')) LIKE '%عدم پاسخ%'
            OR CONCAT_WS(' ', COALESCE(a.activity_type_title, ''), COALESCE(a.title, ''), COALESCE(a.note, ''), COALESCE(a.result_note, ''), COALESCE(a.call_type, '')) LIKE '%بی پاسخ%')";

        $rows = $this->runSelectAll("
            SELECT
                d.id as deal_id,
                d.status,
                DATE(COALESCE(d.register_time, d.created_at)) as deal_date,
                COALESCE(d.payable_amount, 0) as payable_amount,
                COALESCE(pay.total_paid, 0) as total_paid,
                COALESCE(pay.payment_in_range_amount, 0) as payment_in_range_amount,
                pay.first_payment_date,
                COALESCE(act.activity_count, 0) as activity_count,
                COALESCE(act.no_answer_count, 0) as no_answer_count,
                COALESCE(act.successful_activity_count, 0) as successful_activity_count,
                DATE(COALESCE(p.register_time, p.created_at)) as lead_register_date,
                COALESCE(p.sale_status, '') as lead_sale_status,
                COALESCE(p.has_previous_purchase, 0) as has_previous_purchase,
                firstd.first_deal_date
            FROM deals d
            LEFT JOIN persons p ON p.didar_contact_id = d.contact_didar_id
            LEFT JOIN (
                SELECT
                    pr.deal_id,
                    SUM(CASE WHEN {$isSudran} = 0 THEN {$netAmount} ELSE 0 END) as total_paid,
                    SUM(CASE WHEN {$paymentDate} BETWEEN ? AND ? AND ({$isCheque} = 0 OR pr.cheque_collection_status = 'collected') THEN {$netAmount} ELSE 0 END) as payment_in_range_amount,
                    MIN(CASE WHEN {$isSudran} = 0 THEN {$paymentDate} ELSE NULL END) as first_payment_date
                FROM payment_records pr
                WHERE {$valid}
                  AND {$netAmount} > 0
                  AND ({$isCheque} = 0 OR pr.cheque_collection_status = 'collected')
                  AND {$paymentDate} IS NOT NULL
                GROUP BY pr.deal_id
            ) pay ON pay.deal_id = d.id
            LEFT JOIN (
                SELECT
                    a.deal_didar_id,
                    COUNT(*) as activity_count,
                    SUM(CASE WHEN {$noAnswerExpr} THEN 1 ELSE 0 END) as no_answer_count,
                    SUM(CASE WHEN {$noAnswerExpr} THEN 0 ELSE 1 END) as successful_activity_count
                FROM activities a
                GROUP BY a.deal_didar_id
            ) act ON act.deal_didar_id = d.didar_deal_id
            LEFT JOIN (
                SELECT contact_didar_id, MIN(DATE(COALESCE(register_time, created_at))) as first_deal_date
                FROM deals
                GROUP BY contact_didar_id
            ) firstd ON firstd.contact_didar_id = d.contact_didar_id
            WHERE {$whereDeals}
            ORDER BY DATE(COALESCE(d.register_time, d.created_at)) DESC, d.id DESC
        ", array_merge([$dateFrom, $dateTo], $params));

        $ids = [];
        $factsByDeal = [];
        foreach ($rows as $row) {
            $dealId = (int)($row['deal_id'] ?? 0);
            if ($dealId <= 0) {
                continue;
            }
            $status = (string)($row['status'] ?? '');
            $isWon = $status === 'Won';
            $amount = (float)($row['payable_amount'] ?? 0);
            $paid = (float)($row['total_paid'] ?? 0);
            $activityCount = (int)($row['activity_count'] ?? 0);
            $noAnswerCount = (int)($row['no_answer_count'] ?? 0);
            $successActivityCount = (int)($row['successful_activity_count'] ?? 0);
            $dealDate = (string)($row['deal_date'] ?? '');
            $firstPaymentDate = (string)($row['first_payment_date'] ?? '');
            $registeredInRange = $this->uiOracleInRange($dealDate, ['date_from' => $dateFrom, 'date_to' => $dateTo]);
            $firstPaymentInRange = $this->uiOracleInRange($firstPaymentDate, ['date_from' => $dateFrom, 'date_to' => $dateTo]);
            $outsideRegisteredFirstPaymentInRange = !$registeredInRange && $firstPaymentInRange;
            if (!$registeredInRange && !$outsideRegisteredFirstPaymentInRange) {
                continue;
            }
            $isCompleted = $paid > 0 && ($amount <= 0 || $paid >= $amount);
            $isOutstanding = $paid > 0 && !$isCompleted;
            $isReportingSuccessful = $status !== 'Lost' && $paid > 0 && $firstPaymentInRange;
            $paymentOverlay = $paymentOverlayByDeal[$dealId] ?? null;
            $paymentInRangeAmount = is_array($paymentOverlay)
                ? (float)($paymentOverlay['payment_in_range_amount'] ?? 0)
                : (float)($row['payment_in_range_amount'] ?? 0);
            $salesDepositInRangeAmount = is_array($paymentOverlay)
                ? (float)($paymentOverlay['category_amounts']['sales_deposit'] ?? 0)
                : $paymentInRangeAmount;
            $firstDealDate = (string)($row['first_deal_date'] ?? '');
            $leadDate = (string)($row['lead_register_date'] ?? '');
            $isNewLead = $firstDealDate !== '' && $dealDate === $firstDealDate && ($leadDate === '' || $leadDate >= $firstDealDate);
            $saleStatus = trim((string)($row['lead_sale_status'] ?? ''));
            $hadPurchase = (int)($row['has_previous_purchase'] ?? 0) === 1
                || in_array($saleStatus, ['1', 'موفق', 'Won', 'won', 'success', 'successful'], true);

            $match = false;
            switch ($target) {
                case '':
                case 'total_registered_deals':
                    $match = $registeredInRange;
                    break;
                case 'first_payment_outside_registered_deals':
                    $match = $outsideRegisteredFirstPaymentInRange && $isReportingSuccessful;
                    break;
                case 'previous_range_completed_deals':
                    $match = $outsideRegisteredFirstPaymentInRange && $isReportingSuccessful && $isCompleted;
                    break;
                case 'previous_range_outstanding_deals':
                    $match = $outsideRegisteredFirstPaymentInRange && $isReportingSuccessful && $isOutstanding;
                    break;
                case 'no_activity_deals':
                    $match = $registeredInRange && $activityCount === 0;
                    break;
                case 'no_answer_only_deals':
                    $match = $registeredInRange && $activityCount > 0 && $noAnswerCount > 0 && $successActivityCount === 0;
                    break;
                case 'follow_up_deals':
                    $match = $registeredInRange && $paid <= 0 && $status !== 'Lost' && $successActivityCount > 0;
                    break;
                case 'lost_deals':
                    $match = $registeredInRange && $status === 'Lost';
                    break;
                case 'won_deals':
                    $match = $registeredInRange && $isReportingSuccessful && $isCompleted;
                    break;
                case 'won_outstanding_deals':
                    $match = $registeredInRange && $isReportingSuccessful && $isOutstanding;
                    break;
                case 'new_leads':
                    $match = $registeredInRange && $isNewLead;
                    break;
                case 'old_previous_purchase':
                    $match = $registeredInRange && !$isNewLead && $hadPurchase;
                    break;
                case 'old_without_purchase':
                    $match = $registeredInRange && !$isNewLead && !$hadPurchase;
                    break;
            }
            if ($match) {
                $factPaymentAmount = in_array($target, [
                    'first_payment_outside_registered_deals',
                    'previous_range_completed_deals',
                    'previous_range_outstanding_deals',
                    'won_deals',
                    'won_outstanding_deals',
                ], true) ? $salesDepositInRangeAmount : $paymentInRangeAmount;
                $ids[] = $dealId;
                $factsByDeal[$dealId] = [
                    'payment_amount' => $factPaymentAmount,
                    'payments_total_amount' => $factPaymentAmount,
                    'payment_in_range_amount' => $factPaymentAmount,
                ];
            }
        }

        $result = $this->buildSimpleDealLeadDrilldownByIds($ids, $filters);
        foreach ($result['deals'] as &$dealRow) {
            $dealId = (int)($dealRow['deal_id'] ?? 0);
            if ($dealId > 0 && isset($factsByDeal[$dealId])) {
                $dealRow = array_merge($dealRow, $factsByDeal[$dealId]);
            }
        }
        unset($dealRow);
        $result['meta']['total_payment_amount'] = array_sum(array_map(static function ($row) {
            return (float)($row['payment_in_range_amount'] ?? 0);
        }, $factsByDeal));
        return $result;
    }

    private function getCeoChequeStatusDrilldown(array $filters, string $metricKey, array $rowContext): array
    {
        $dealWhere = $this->buildWhereClauseForDealsNoDate($filters, 'd');
        $dealParams = $this->buildParamsForDealsNoDate($filters);
        $isCheque = $this->ceoPaymentIsChequeExpr('pr');
        $valid = $this->ceoPaymentValidWhere('pr');
        $netAmount = $this->ceoPaymentNetAmountExpr('pr');
        $chequeWindowDate = "DATE(COALESCE(pr.cheque_collection_date, pr.cheque_due_date, pr.effective_date, pr.payment_date, pr.created_at))";
        $today = (new \DateTimeImmutable('now', new \DateTimeZone('Asia/Tehran')))->format('Y-m-d');
        $currentJalaliRange = DateNormalizer::currentJalaliMonthRange($today);
        $currentJalaliMonthStart = (string)($currentJalaliRange['date_from'] ?? $today);
        $targetKey = trim((string)($rowContext['key'] ?? $rowContext['category'] ?? ''));
        $params = [];
        $extra = '';
        if ($metricKey === 'cheque_status') {
            $status = trim((string)($rowContext['status'] ?? ''));
            if ($status !== '') {
                $extra = " AND COALESCE(pr.cheque_status, 'pending') = ?";
                $params[] = $status;
            }
        } elseif ($metricKey === 'cheque_collection_status') {
            $status = trim((string)($rowContext['status'] ?? ''));
            if ($status !== '') {
                $extra = " AND COALESCE(pr.cheque_collection_status, 'pending') = ?";
                $params[] = $status;
            }
        }

        $rows = $this->runSelectAll("
            SELECT
                pr.id as payment_record_id,
                pr.deal_id,
                pr.deal_didar_id,
                pr.payment_uuid,
                pr.source_type,
                {$netAmount} as net_amount,
                {$chequeWindowDate} as payment_window_date,
                {$this->ceoPaymentRegisteredDateExpr('pr')} as registered_date,
                1 as is_cheque,
                COALESCE(pr.cheque_status, 'pending') as cheque_status,
                COALESCE(pr.cheque_collection_status, 'pending') as cheque_collection_status,
                pr.cheque_due_date,
                pr.cheque_collection_date,
                COALESCE(d.title, '') as deal_title,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name
            FROM payment_records pr
            INNER JOIN deals d ON d.id = pr.deal_id
            LEFT JOIN users u ON u.didar_user_id = d.owner_didar_id
            WHERE {$valid}
              AND {$isCheque}
              {$extra}
              AND {$dealWhere}
            ORDER BY {$chequeWindowDate} DESC, pr.id DESC
        ", array_merge($params, $dealParams));

        if ($metricKey === 'cheque_state' || ($metricKey === 'all_cheques_count' && $targetKey !== '')) {
            $rows = array_values(array_filter($rows, function ($row) use ($targetKey, $today, $currentJalaliMonthStart) {
                $collection = (string)($row['cheque_collection_status'] ?? 'pending');
                $status = (string)($row['cheque_status'] ?? 'pending');
                $dueDate = trim((string)($row['cheque_due_date'] ?? ''));
                $isReturned = in_array($collection, ['returned', 'bounced'], true) || in_array($status, ['returned', 'rejected'], true);
                $isClosed = $collection === 'collected' || $isReturned;
                if ($targetKey === 'returned_cheques') {
                    return $isReturned;
                }
                if ($targetKey === 'collected_cheques') {
                    return $collection === 'collected';
                }
                if ($targetKey === 'due_unresolved_cheques') {
                    return !$isClosed && $dueDate !== '' && $dueDate <= $today;
                }
                if ($targetKey === 'undated_pending_cheques') {
                    return !$isClosed && $dueDate === '';
                }
                if (strpos($targetKey, 'cheque_due_month_') === 0) {
                    if ($isClosed || $dueDate === '' || $dueDate <= $today || $dueDate < $currentJalaliMonthStart) {
                        return false;
                    }
                    $month = $this->ceoJalaliMonthLabel($dueDate);
                    return $targetKey === 'cheque_due_month_' . $month['key'];
                }
                return true;
            }));
        }

        $result = $this->buildCeoPaymentDrilldown($rows, $filters);
        if (!isset($result['meta']) || !is_array($result['meta'])) {
            $result['meta'] = [];
        }
        $result['meta']['drilldown_kind'] = 'cheque';
        $result['meta']['row_type'] = 'payment';
        $result['meta']['title'] = 'وضعیت چک‌ها';
        return $result;
    }

    /**
     * طبقه‌بندی معاملات دارای پرداخت در بازه «تاریخ پرداخت»:
     * - مبنای انتخاب معامله: حداقل یک پرداخت در بازه
     * - سپس وضعیت تسویه/بدهکاری بر اساس total_paid همه‌زمان + payable_amount همان معامله
     */
    private function getPaymentWindowDealClassification(array $filters): array
    {
        if ($this->getKpiPaymentDateBasis($filters, 'payment') === 'ui_payment_date') {
            return $this->getPaymentWindowDealClassificationUiOracle($filters);
        }
        $pdo = $this->db->getPdo();
        $paymentsByDealDateSql = $this->buildKpiPaymentsWindowByDealDateSql($filters, 'sales');
        $paymentsByDealSql = $this->buildKpiAllTimePaymentTotalsByDealSql($filters, 'payment');
        $whereDealsNoDate = $this->buildWhereClauseForDealsNoDate($filters, 'd');
        $paramsDealsNoDate = $this->buildParamsForDealsNoDate($filters);

        $paymentDateWhere = [];
        $paymentDateParams = [];
        if (!empty($filters['date_from'])) {
            $paymentDateWhere[] = "pbd.payment_date >= ?";
            $paymentDateParams[] = $filters['date_from'];
        }
        if (!empty($filters['date_to'])) {
            $paymentDateWhere[] = "pbd.payment_date <= ?";
            $paymentDateParams[] = $filters['date_to'];
        }
        $paymentDateClause = !empty($paymentDateWhere) ? implode(' AND ', $paymentDateWhere) : '1=1';

        $sql = "
            SELECT
                COUNT(DISTINCT d.id) as paid_deals_count,
                SUM(COALESCE(d.payable_amount, 0)) as paid_deals_contract_sum,
                SUM(COALESCE(tp.total_paid, 0)) as paid_deals_collected_sum,

                SUM(CASE
                    WHEN COALESCE(d.payable_amount, 0) > 0 AND COALESCE(tp.total_paid, 0) < COALESCE(d.payable_amount, 0) THEN 1
                    ELSE 0
                END) as outstanding_count,
                SUM(CASE
                    WHEN COALESCE(d.payable_amount, 0) > 0 AND COALESCE(tp.total_paid, 0) < COALESCE(d.payable_amount, 0)
                    THEN (COALESCE(d.payable_amount, 0) - COALESCE(tp.total_paid, 0))
                    ELSE 0
                END) as outstanding_amount_sum,
                SUM(CASE
                    WHEN COALESCE(d.payable_amount, 0) > 0 AND COALESCE(tp.total_paid, 0) < COALESCE(d.payable_amount, 0)
                    THEN COALESCE(d.payable_amount, 0)
                    ELSE 0
                END) as outstanding_contract_sum,
                SUM(CASE
                    WHEN COALESCE(d.payable_amount, 0) > 0 AND COALESCE(tp.total_paid, 0) < COALESCE(d.payable_amount, 0)
                    THEN COALESCE(tp.total_paid, 0)
                    ELSE 0
                END) as outstanding_collected_sum,

                SUM(CASE
                    WHEN (COALESCE(d.payable_amount, 0) <= 0 AND COALESCE(tp.total_paid, 0) > 0)
                         OR (COALESCE(d.payable_amount, 0) > 0 AND COALESCE(tp.total_paid, 0) >= COALESCE(d.payable_amount, 0))
                    THEN 1
                    ELSE 0
                END) as settled_count,
                SUM(CASE
                    WHEN (COALESCE(d.payable_amount, 0) <= 0 AND COALESCE(tp.total_paid, 0) > 0)
                         OR (COALESCE(d.payable_amount, 0) > 0 AND COALESCE(tp.total_paid, 0) >= COALESCE(d.payable_amount, 0))
                    THEN COALESCE(d.payable_amount, 0)
                    ELSE 0
                END) as settled_contract_sum,
                SUM(CASE
                    WHEN (COALESCE(d.payable_amount, 0) <= 0 AND COALESCE(tp.total_paid, 0) > 0)
                         OR (COALESCE(d.payable_amount, 0) > 0 AND COALESCE(tp.total_paid, 0) >= COALESCE(d.payable_amount, 0))
                    THEN COALESCE(tp.total_paid, 0)
                    ELSE 0
                END) as settled_collected_sum
            FROM deals d
            INNER JOIN (
                SELECT DISTINCT pbd.deal_id
                FROM ({$paymentsByDealDateSql}) pbd
                WHERE {$paymentDateClause}
            ) pw ON pw.deal_id = d.id
            LEFT JOIN ({$paymentsByDealSql}) tp ON tp.deal_id = d.id
            WHERE {$whereDealsNoDate}
        ";

        $stmt = $pdo->prepare($sql);
        $stmt->execute(array_merge($paymentDateParams, $paramsDealsNoDate));
        return $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
    }

    /**
     * هدف KPI برای کاربر
     */
    public function getKpiTarget(int $userId, int $year, int $month): ?array
    {
        $pdo = $this->db->getPdo();
        $stmt = $pdo->prepare("
            SELECT * FROM kpi_targets
            WHERE user_id = ? AND year = ? AND month = ? AND is_active = 1
        ");
        $stmt->execute([$userId, $year, $month]);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        return $result ?: null;
    }

    public function upsertKpiTarget(int $userId, int $year, int $month, float $targetAmount, int $targetDealCount = 0): bool
    {
        $pdo = $this->db->getPdo();
        $stmt = $pdo->prepare("
            INSERT INTO kpi_targets (user_id, year, month, target_amount, target_deal_count)
            VALUES (?, ?, ?, ?, ?)
            ON DUPLICATE KEY UPDATE
                target_amount = VALUES(target_amount),
                target_deal_count = VALUES(target_deal_count),
                updated_at = CURRENT_TIMESTAMP
        ");

        return $stmt->execute([$userId, $year, $month, $targetAmount, $targetDealCount]);
    }

    public function getAllKpiTargets(): array
    {
        $pdo = $this->db->getPdo();
        $stmt = $pdo->prepare("
            SELECT
                kt.*,
                u.first_name,
                u.last_name,
                u.display_name,
                u.didar_user_id,
                u.role
            FROM kpi_targets kt
            INNER JOIN users u ON kt.user_id = u.id
            WHERE kt.is_active = 1
            ORDER BY kt.year DESC, kt.month DESC, u.first_name ASC
        ");
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    /**
     * لیست معاملات برای جدول KPI
     */
    public function getKpiDealDetails(array $filters): array
    {
        $pdo = $this->db->getPdo();
        $where = $this->buildWhereClauseForDealDetails($filters);
        $params = $this->buildParamsForDeals($filters);
        $paymentsByDealSql = $this->buildUnifiedPaymentsByDealSql();

        $sql = "
            SELECT
                d.id,
                d.didar_deal_id,
                d.title,
                d.status,
                d.payable_amount,
                d.register_time,
                d.won_time,
                d.lost_time,
                u.display_name as owner_name,
                u.didar_user_id as owner_didar_id,
                COALESCE(pay.total_paid, 0) as collected_amount,
                pay.first_payment_date as first_payment_date,
                CASE WHEN COALESCE(pay.total_paid, 0) > 0 THEN 1 ELSE 0 END as payment_count
            FROM deals d
            LEFT JOIN users u ON d.owner_didar_id = u.didar_user_id
            LEFT JOIN ({$paymentsByDealSql}) pay ON pay.deal_id = d.id
            WHERE {$where}
            ORDER BY d.register_time DESC
        ";

        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    /**
     * Drill-down: رکوردهای پشت یک سلول KPI (لید + معامله + فعالیت)
     */
    public function getKpiDrilldown(array $filters, string $tableId, string $metricKey, array $rowContext): array
    {
        $tableId = trim($tableId);
        $metricKey = trim($metricKey);
        $rowDate = trim((string)($rowContext['date'] ?? ''));
        $rowOwnerDidarId = trim((string)($rowContext['owner_didar_id'] ?? ''));
        $rowProductName = trim((string)($rowContext['product_name'] ?? ''));
        $rowFailureReason = trim((string)($rowContext['failure_reason'] ?? ''));
        $rowActivityType = trim((string)($rowContext['activity_type'] ?? ''));

        if ($tableId === '' || $metricKey === '') {
            return ['leads' => [], 'deals' => [], 'activities' => [], 'payments' => [], 'meta' => ['total_leads' => 0, 'total_deals' => 0, 'total_activities' => 0, 'total_payments' => 0]];
        }

        if ($tableId === 'activity_status' || $tableId === 'activity_summary' || $tableId === 'activity_type_details') {
            return $this->getActivityStatusDrilldown($filters, $metricKey, $rowDate, $rowOwnerDidarId, $rowActivityType);
        }

        if ($tableId === 'lead_status') {
            return $this->getLeadStatusDrilldown($filters, $metricKey, $rowDate, $rowOwnerDidarId);
        }

        if ($tableId === 'performance_cards') {
            return $this->getPerformanceCardDrilldown($filters, $metricKey, $rowOwnerDidarId);
        }
        if ($tableId === 'performance_charts') {
            return $this->getPerformanceChartDrilldown($filters, $metricKey, $rowContext);
        }
        if ($tableId === 'performance_ceo_report') {
            return $this->getCeoPerformanceDrilldown($filters, $metricKey, $rowContext);
        }

        return $this->getDealBasedDrilldown(
            $filters,
            $tableId,
            $metricKey,
            $rowDate,
            $rowOwnerDidarId,
            $rowProductName,
            $rowFailureReason
        );
    }

    private function getPerformanceChartDrilldown(array $filters, string $metricKey, array $rowContext): array
    {
        $rowDate = trim((string)($rowContext['date'] ?? ''));
        $rowProductName = trim((string)($rowContext['product_name'] ?? ''));
        $rowStage = trim((string)($rowContext['stage'] ?? ''));

        if ($metricKey === 'daily_sales_point') {
            return $this->getDailySalesChartPointDrilldown($filters, $rowDate);
        }
        if ($metricKey === 'product_sales_share_item') {
            return $this->getProductSalesShareChartDrilldown($filters, $rowProductName);
        }
        if ($metricKey === 'conversion_funnel_stage') {
            return $this->getConversionFunnelChartDrilldown($filters, $rowStage);
        }

        return ['leads' => [], 'deals' => [], 'activities' => [], 'payments' => [], 'meta' => ['total_leads' => 0, 'total_deals' => 0, 'total_activities' => 0, 'total_payments' => 0]];
    }

    private function getPerformanceCardDrilldown(array $filters, string $metricKey, string $rowOwnerDidarId): array
    {
        if ($metricKey === 'sales_total') {
            return $this->getSalesTotalCardDrilldown($filters, $rowOwnerDidarId);
        }
        if ($metricKey === 'deal_conversion_rate') {
            return $this->getDealConversionAnalysisDrilldown($filters, $rowOwnerDidarId);
        }
        if (in_array($metricKey, ['deal_related_total_count', 'deal_registered_in_range_count', 'deal_outside_registered_first_payment_count'], true)) {
            return $this->getPerformanceRelatedDealsCardDrilldown($filters, $metricKey, $rowOwnerDidarId);
        }
        if (in_array($metricKey, ['payment_related_total_amount', 'payment_registered_in_range_amount', 'payment_outside_registered_first_payment_amount'], true)) {
            return $this->getPerformanceRelatedPaymentsCardDrilldown($filters, $metricKey, $rowOwnerDidarId);
        }
        if ($metricKey === 'lost_deposit_amount') {
            if ($rowOwnerDidarId !== '') {
                $filters['owner_didar_id'] = $rowOwnerDidarId;
                $filters['owner_didar_ids_csv'] = $rowOwnerDidarId;
                $filters['owner_didar_ids'] = [$rowOwnerDidarId];
            }
            return $this->getCeoPerformanceDrilldown($filters, 'lost_deposit_amount', []);
        }
        if ($metricKey === 'lead_conversion_rate') {
            return $this->getLeadConversionLeadAnalysisDrilldown($filters, $rowOwnerDidarId, true);
        }
        if ($metricKey === 'lead_converted') {
            return $this->getLeadConversionLeadAnalysisDrilldown($filters, $rowOwnerDidarId, false);
        }
        if ($metricKey === 'settled_count') {
            return $this->getFinancialPerformanceCardDrilldownUiOracle($filters, 'settled', $rowOwnerDidarId);
        }
        if ($metricKey === 'outstanding_amount_sum') {
            return $this->getFinancialPerformanceCardDrilldownUiOracle($filters, 'outstanding', $rowOwnerDidarId);
        }
        if ($metricKey === 'without_payment_count') {
            return $this->getWithoutPaymentCardDrilldown($filters, $rowOwnerDidarId);
        }
        if ($metricKey === 'open_pipeline_count') {
            return $this->getPerformancePartitionCardDrilldown($filters, 'open_valid', $rowOwnerDidarId);
        }
        if ($metricKey === 'won_count') {
            return $this->getPerformancePartitionCardDrilldown($filters, 'won_valid', $rowOwnerDidarId);
        }
        if ($metricKey === 'lost_count') {
            return $this->getPerformancePartitionCardDrilldown($filters, 'lost_valid', $rowOwnerDidarId);
        }
        if (strpos($metricKey, 'activity_perf_') === 0) {
            return $this->getActivityPerformanceCardDrilldown($filters, $metricKey, $rowOwnerDidarId);
        }

        return ['leads' => [], 'deals' => [], 'activities' => [], 'payments' => [], 'meta' => ['total_leads' => 0, 'total_deals' => 0, 'total_activities' => 0, 'total_payments' => 0]];
    }

    private function getPerformanceRelatedDealsCardDrilldown(array $filters, string $metricKey, string $rowOwnerDidarId = ''): array
    {
        if ($rowOwnerDidarId !== '') {
            $filters['owner_didar_id'] = $rowOwnerDidarId;
            $filters['owner_didar_ids_csv'] = $rowOwnerDidarId;
            $filters['owner_didar_ids'] = [$rowOwnerDidarId];
        }
        if ($this->getKpiPaymentDateBasis($filters, 'sales') !== 'ui_payment_date') {
            return $this->getDealBasedDrilldown($filters, 'deal_status', 'deal_total', '', $rowOwnerDidarId, '', '');
        }

        $oracle = $this->getUiOracleKpiData($filters);
        $setKey = 'conversion_cohort';
        if ($metricKey === 'deal_registered_in_range_count') {
            $setKey = 'conversion_registered_in_range';
        } elseif ($metricKey === 'deal_outside_registered_first_payment_count') {
            $setKey = 'conversion_cross_date_sales_added';
        }
        $ids = array_map('intval', array_keys($oracle['sets'][$setKey] ?? []));
        return $this->buildSimpleDealLeadDrilldownByIds($ids, $filters);
    }

    private function getPerformanceRelatedPaymentsCardDrilldown(array $filters, string $metricKey, string $rowOwnerDidarId = ''): array
    {
        if ($rowOwnerDidarId !== '') {
            $filters['owner_didar_id'] = $rowOwnerDidarId;
            $filters['owner_didar_ids_csv'] = $rowOwnerDidarId;
            $filters['owner_didar_ids'] = [$rowOwnerDidarId];
        }

        if ($this->tableHasColumns('payment_records', ['id', 'deal_id', 'amount'])) {
            $target = 'total';
            if ($metricKey === 'payment_registered_in_range_amount') {
                $target = 'registered_in_range';
            } elseif ($metricKey === 'payment_outside_registered_first_payment_amount') {
                $target = 'outside_registered';
            }
            $rows = [];
            foreach ($this->getCeoPaymentRows($filters) as $row) {
                if ($this->classifyCeoPaymentRow($row, $filters) === '') {
                    continue;
                }
                $inside = $this->ceoDateInFilterRange((string)($row['deal_registered_date'] ?? ''), $filters);
                if ($target === 'registered_in_range' && !$inside) {
                    continue;
                }
                if ($target === 'outside_registered' && $inside) {
                    continue;
                }
                $rows[] = $row;
            }
            return $this->buildCeoPaymentDrilldown($rows, $filters);
        }

        $oracle = $this->getUiOracleKpiData($filters);
        $setKey = 'deposit_related_total';
        if ($metricKey === 'payment_registered_in_range_amount') {
            $setKey = 'deposit_registered_in_range';
        } elseif ($metricKey === 'payment_outside_registered_first_payment_amount') {
            $setKey = 'deposit_outside_registered_first_payment';
        }

        $ids = array_map('intval', array_keys($oracle['sets'][$setKey] ?? []));
        return $this->buildSimpleDealLeadDrilldownByIds($ids, $filters, 'payment');
    }

    private function getPerformancePaymentRangeSplit(array $filters): array
    {
        if (!$this->tableHasColumns('payment_records', ['id', 'deal_id', 'amount'])) {
            return [];
        }

        $split = [
            'total' => ['amount' => 0.0, 'payment_count' => 0, 'deal_ids' => []],
            'registered_in_range' => ['amount' => 0.0, 'payment_count' => 0, 'deal_ids' => []],
            'outside_registered' => ['amount' => 0.0, 'payment_count' => 0, 'deal_ids' => []],
            'categories' => [
                'sales_deposit' => ['amount' => 0.0, 'payment_count' => 0, 'deal_ids' => []],
                'balance_deposit' => ['amount' => 0.0, 'payment_count' => 0, 'deal_ids' => []],
                'cheque_collected' => ['amount' => 0.0, 'payment_count' => 0, 'deal_ids' => []],
                'sudran_deposit' => ['amount' => 0.0, 'payment_count' => 0, 'deal_ids' => []],
                'lost_deposit' => ['amount' => 0.0, 'payment_count' => 0, 'deal_ids' => []],
            ],
        ];
        foreach ($this->getCeoPaymentRows($filters) as $row) {
            $category = $this->classifyCeoPaymentRow($row, $filters);
            if ($category === '') {
                continue;
            }
            $dealId = (int)($row['deal_id'] ?? 0);
            $amount = (float)($row['net_amount'] ?? 0);
            if ($dealId <= 0 || $amount <= 0) {
                continue;
            }
            $bucket = $this->ceoDateInFilterRange((string)($row['deal_registered_date'] ?? ''), $filters)
                ? 'registered_in_range'
                : 'outside_registered';
            foreach (['total', $bucket] as $key) {
                $split[$key]['amount'] += $amount;
                $split[$key]['payment_count']++;
                $split[$key]['deal_ids'][$dealId] = true;
            }
            if (isset($split['categories'][$category])) {
                $split['categories'][$category]['amount'] += $amount;
                $split['categories'][$category]['payment_count']++;
                $split['categories'][$category]['deal_ids'][$dealId] = true;
            }
        }
        foreach ($split as $key => $row) {
            if ($key === 'categories') {
                foreach ($row as $category => $categoryRow) {
                    $split['categories'][$category]['deal_count'] = count($categoryRow['deal_ids'] ?? []);
                    unset($split['categories'][$category]['deal_ids']);
                }
                continue;
            }
            $split[$key]['deal_count'] = count($row['deal_ids'] ?? []);
            unset($split[$key]['deal_ids']);
        }
        return $split;
    }

    private function buildActivityPerformanceSnapshot(array $filters): array
    {
        $state = $this->buildActivityPerformanceState($filters);
        $card = static function (array $base, array $metrics, string $stageMetricKey = ''): array {
            if ($stageMetricKey !== '') {
                $performedActivityCount = (int)($base['count'] ?? 0);
                $stageCount = (int)($metrics[$stageMetricKey]['count'] ?? 0);
                $base['count'] = $stageCount;
                $base['stage_count'] = $stageCount;
                $base['stage_metric_key'] = $stageMetricKey;
                $base['performed_activity_count'] = $performedActivityCount;
                $base['primary_count_label'] = 'تعداد معاملات در مرحله کاریز';
            }
            return $base;
        };
        return [
            'past_deals' => [
                $card([
                    'key' => 'activity_perf_past_no_answer',
                    'title' => 'تماس بی پاسخ',
                    'count' => count($state['drilldown']['activity_perf_past_no_answer']['activities'] ?? []),
                    'duration_minutes' => (int)($state['metrics']['activity_perf_past_no_answer']['duration_minutes'] ?? 0),
                ], $state['metrics'], 'activity_perf_past_no_answer_stage'),
                $card([
                    'key' => 'activity_perf_past_outstanding_followup',
                    'title' => 'پیگیری مانده حساب',
                    'count' => count($state['drilldown']['activity_perf_past_outstanding_followup']['activities'] ?? []),
                    'duration_minutes' => (int)($state['metrics']['activity_perf_past_outstanding_followup']['duration_minutes'] ?? 0),
                ], $state['metrics'], 'activity_perf_past_outstanding_followup_stage'),
                $card([
                    'key' => 'activity_perf_past_payment_link',
                    'title' => 'ارسال لینک پرداخت',
                    'count' => count($state['drilldown']['activity_perf_past_payment_link']['activities'] ?? []),
                    'duration_minutes' => (int)($state['metrics']['activity_perf_past_payment_link']['duration_minutes'] ?? 0),
                ], $state['metrics'], 'activity_perf_past_payment_link_stage'),
                [
                    'key' => 'activity_perf_past_outstanding_deals',
                    'title' => 'تعداد واریزی مانده حساب',
                    'count' => (int)($state['metrics']['activity_perf_past_outstanding_deals']['paid_today_count'] ?? 0),
                    'sub_count' => (int)($state['metrics']['activity_perf_past_outstanding_deals']['count'] ?? count($state['drilldown']['activity_perf_past_outstanding_deals']['deal_ids'] ?? [])),
                    'sub_count_label' => 'تعداد کل معاملات دارای مانده حساب',
                ],
                [
                    'key' => 'activity_perf_past_outstanding_amount',
                    'title' => 'واریزی مانده حساب',
                    'amount' => (float)($state['metrics']['activity_perf_past_outstanding_amount']['paid_today_amount'] ?? 0),
                    'sub_amount' => (float)($state['metrics']['activity_perf_past_outstanding_amount']['amount'] ?? 0),
                    'sub_amount_label' => 'مبلغ کل مانده حساب',
                    'primary_value_type' => 'amount',
                ],
                [
                    'key' => 'activity_perf_past_no_contact',
                    'title' => 'عدم تماس',
                    'count' => count($state['drilldown']['activity_perf_past_no_contact']['deal_ids'] ?? []),
                ],
                $card([
                    'key' => 'activity_perf_past_followup_call',
                    'title' => 'تماس پیگیری',
                    'count' => count($state['drilldown']['activity_perf_past_followup_call']['activities'] ?? []),
                    'duration_minutes' => (int)($state['metrics']['activity_perf_past_followup_call']['duration_minutes'] ?? 0),
                ], $state['metrics'], 'activity_perf_past_followup_call_stage'),
            ],
            'daily' => [
                [
                    'key' => 'activity_perf_current_registered_deals',
                    'title' => 'تعداد کل معاملات ثبت‌شده',
                    'count' => count($state['drilldown']['activity_perf_current_registered_deals']['deal_ids'] ?? []),
                ],
                $card([
                    'key' => 'activity_perf_current_no_answer',
                    'title' => 'تماس بی پاسخ',
                    'count' => count($state['drilldown']['activity_perf_current_no_answer']['activities'] ?? []),
                    'duration_minutes' => (int)($state['metrics']['activity_perf_current_no_answer']['duration_minutes'] ?? 0),
                ], $state['metrics'], 'activity_perf_current_no_answer_stage'),
                $card([
                    'key' => 'activity_perf_current_payment_link',
                    'title' => 'ارسال لینک پرداخت',
                    'count' => count($state['drilldown']['activity_perf_current_payment_link']['activities'] ?? []),
                    'duration_minutes' => (int)($state['metrics']['activity_perf_current_payment_link']['duration_minutes'] ?? 0),
                ], $state['metrics'], 'activity_perf_current_payment_link_stage'),
                $card([
                    'key' => 'activity_perf_current_followup_call',
                    'title' => 'تماس پیگیری',
                    'count' => count($state['drilldown']['activity_perf_current_followup_call']['activities'] ?? []),
                    'duration_minutes' => (int)($state['metrics']['activity_perf_current_followup_call']['duration_minutes'] ?? 0),
                ], $state['metrics'], 'activity_perf_current_followup_call_stage'),
                [
                    'key' => 'activity_perf_current_no_contact',
                    'title' => 'عدم تماس',
                    'count' => count($state['drilldown']['activity_perf_current_no_contact']['deal_ids'] ?? []),
                ],
            ],
        ];
    }

    private function getActivityPerformanceCohortDeals(array $filters, string $mode): array
    {
        $whereDeals = $this->buildWhereClauseForDealsNoDate($filters, 'd');
        $paramsDeals = $this->buildParamsForDealsNoDate($filters);
        [$dateFrom, $dateTo] = $this->dateRangeFromFilters($filters);
        $dateClause = $mode === 'past'
            ? "DATE(COALESCE(d.register_time, d.created_at)) < ?"
            : "DATE(COALESCE(d.register_time, d.created_at)) BETWEEN ? AND ?";
        $params = $mode === 'past'
            ? array_merge([$dateFrom], $paramsDeals)
            : array_merge([$dateFrom, $dateTo], $paramsDeals);

        return $this->runSelectAll("
            SELECT
                d.id,
                COALESCE(d.didar_deal_id, '') as didar_deal_id,
                COALESCE(d.payable_amount, 0) as payable_amount
            FROM deals d
            WHERE {$dateClause}
              AND {$whereDeals}
        ", $params);
    }

    private function buildActivityPerformanceState(array $filters, string $rowOwnerDidarId = ''): array
    {
        if ($rowOwnerDidarId !== '') {
            $filters['owner_didar_id'] = $rowOwnerDidarId;
            $filters['owner_didar_ids_csv'] = $rowOwnerDidarId;
            $filters['owner_didar_ids'] = [$rowOwnerDidarId];
        }

        [$dateFrom, $dateTo] = $this->dateRangeFromFilters($filters);
        $activityTypeExpr = $this->buildCanonicalActivityTypeExpr('a');
        $oracleState = $this->buildUiOracleState($filters);
        $oracleKpi = $this->getUiOracleKpiData($filters);
        $salesFirstAllAgg = $oracleKpi['aggs']['sales_first_all'] ?? [];

        $activityOwnerWhere = '';
        $activityOwnerParams = [];
        if (!empty($filters['owner_didar_ids_csv'])) {
            $activityOwnerWhere = " AND FIND_IN_SET(a.owner_didar_id, ?)";
            $activityOwnerParams[] = $filters['owner_didar_ids_csv'];
        } elseif (!empty($filters['owner_didar_id'])) {
            $activityOwnerWhere = " AND a.owner_didar_id = ?";
            $activityOwnerParams[] = $filters['owner_didar_id'];
        }

        $pastDeals = [];
        $dayDeals = [];
        foreach (($oracleState['deals'] ?? []) as $dealId => $deal) {
            $dealDate = (string)($deal['deal_date'] ?? '');
            $effectiveDealDate = $this->resolveActivityPerformanceDealDate($dealId, $deal, $salesFirstAllAgg);
            if ($effectiveDealDate === '') {
                continue;
            }
            $deal['activity_effective_date'] = $effectiveDealDate;
            if ($effectiveDealDate < $dateFrom) {
                if ($this->uiOracleDealPassNoDate($deal, $oracleState, $filters)) {
                    $pastDeals[] = $deal;
                }
                continue;
            }
            if ($this->uiOracleInRange($effectiveDealDate, $filters) && $this->uiOracleDealPassNoDate($deal, $oracleState, $filters)) {
                $dayDeals[] = $deal;
            }
        }

        $allDeals = array_merge($pastDeals, $dayDeals);
        $effectiveDealDateById = [];
        foreach ($allDeals as $dealRow) {
            $dealId = (int)($dealRow['id'] ?? 0);
            $effectiveDate = trim((string)($dealRow['activity_effective_date'] ?? $dealRow['deal_date'] ?? ''));
            if ($dealId > 0 && $effectiveDate !== '') {
                $effectiveDealDateById[$dealId] = $effectiveDate;
            }
        }
        $allDealIds = array_values(array_unique(array_map(static function ($row) {
            return (int)($row['id'] ?? 0);
        }, $allDeals)));
        $allDealDidarIds = array_values(array_filter(array_unique(array_map(static function ($row) {
            return trim((string)($row['didar_deal_id'] ?? ''));
        }, $allDeals))));

        $activityPresence = [];
        $activityRows = [];
        if (!empty($allDealIds)) {
            $dealPlaceholders = implode(',', array_fill(0, count($allDealIds), '?'));
            $activityRows = $this->runSelectAll("
                SELECT
                    a.id as activity_id,
                    DATE(COALESCE(a.register_date, a.created_at)) as activity_date,
                    {$activityTypeExpr} as activity_type,
                    COALESCE(a.duration, 0) as activity_duration_minutes,
                    COALESCE(a.owner_didar_id, '') as activity_owner_didar_id,
                    COALESCE(au.display_name, 'بدون مسئول') as owner_name,
                    d.id as deal_id,
                    COALESCE(d.didar_deal_id, '') as didar_deal_id,
                    COALESCE(d.title, '') as deal_title,
                    DATE(COALESCE(d.register_time, d.created_at)) as deal_date,
                    COALESCE(d.pipeline_stage, '') as pipeline_stage,
                    COALESCE(d.contact_didar_id, '') as contact_didar_id,
                    TRIM(CONCAT(COALESCE(p.first_name, ''), ' ', COALESCE(p.last_name, ''))) as lead_name,
                    COALESCE(p.mobile_phone, '') as mobile_phone
                FROM activities a
                INNER JOIN deals d ON d.didar_deal_id = a.deal_didar_id
                LEFT JOIN users au ON au.didar_user_id = a.owner_didar_id
                LEFT JOIN persons p ON p.didar_contact_id = d.contact_didar_id
                WHERE DATE(COALESCE(a.register_date, a.created_at)) BETWEEN ? AND ?
                  AND d.id IN ({$dealPlaceholders})
                  {$activityOwnerWhere}
                ORDER BY activity_date DESC, a.id DESC
            ", array_merge([$dateFrom, $dateTo], $allDealIds, $activityOwnerParams));

            if (!empty($allDealDidarIds)) {
                foreach ($this->runSelectAll("
                    SELECT DISTINCT COALESCE(a.deal_didar_id, '') as deal_didar_id
                    FROM activities a
                    WHERE a.deal_didar_id IN (" . implode(',', array_fill(0, count($allDealDidarIds), '?')) . ")
                ", $allDealDidarIds) as $presenceRow) {
                    $dealDidarId = trim((string)($presenceRow['deal_didar_id'] ?? ''));
                    if ($dealDidarId !== '') {
                        $activityPresence[$dealDidarId] = true;
                    }
                }
            }
        }

        $paymentTotals = !empty($allDealIds)
            ? $this->getCeoAllTimeCustomerPaymentTotalsByDeal($filters, $allDealIds)
            : [];

        $pastOutstandingDealIds = [];
        $pastOutstandingAmount = 0.0;
        $pastOutstandingPaidTodayDealIds = [];
        $pastNoContactDealIds = [];
        foreach ($pastDeals as $dealRow) {
            $dealId = (int)($dealRow['id'] ?? 0);
            $payable = (float)($dealRow['payable_amount'] ?? 0);
            $totalPaid = (float)($paymentTotals[$dealId] ?? 0);
            if ($payable > 0 && $totalPaid > 0 && $totalPaid < $payable) {
                $pastOutstandingDealIds[] = $dealId;
                $pastOutstandingAmount += ($payable - $totalPaid);
            }
            $pipelineStage = trim((string)($dealRow['pipeline_stage'] ?? ''));
            if ($dealId > 0 && $pipelineStage === 'معامله جدید') {
                $pastNoContactDealIds[] = $dealId;
            }
        }

        $dayDealIds = array_values(array_filter(array_map(static function ($row) {
            return (int)($row['id'] ?? 0);
        }, $dayDeals)));
        $dayNoContactDealIds = [];
        foreach ($dayDeals as $dealRow) {
            $dealId = (int)($dealRow['id'] ?? 0);
            $pipelineStage = trim((string)($dealRow['pipeline_stage'] ?? ''));
            if ($dealId > 0 && $pipelineStage === 'معامله جدید') {
                $dayNoContactDealIds[] = $dealId;
            }
        }

        $balanceDepositPaymentRows = [];
        $oldOutstandingPaymentAmount = 0.0;
        [$paymentDateFrom, $paymentDateTo] = $this->dateRangeFromFilters($filters);
        foreach ($this->getCeoLeanPaymentRowsBetween($filters, $paymentDateFrom, $paymentDateTo) as $paymentRow) {
            if ($this->classifyCeoPaymentRow($paymentRow, $filters) !== 'balance_deposit') {
                continue;
            }
            $dealId = (int)($paymentRow['deal_id'] ?? 0);
            $amount = (float)($paymentRow['net_amount'] ?? 0);
            if ($dealId <= 0 || $amount <= 0) {
                continue;
            }
            $balanceDepositPaymentRows[] = $paymentRow;
            $oldOutstandingPaymentAmount += $amount;
            $pastOutstandingPaidTodayDealIds[$dealId] = $dealId;
        }

        $stageMetricMap = [
            'activity_perf_past_no_answer_stage' => ['deals' => $pastDeals, 'title' => 'تماس عدم پاسخ'],
            'activity_perf_past_outstanding_followup_stage' => ['deals' => $pastDeals, 'title' => 'پیگیری مانده حساب'],
            'activity_perf_past_payment_link_stage' => ['deals' => $pastDeals, 'title' => 'ارسال لینک پرداخت'],
            'activity_perf_past_followup_call_stage' => ['deals' => $pastDeals, 'title' => 'تماس پیگیری'],
            'activity_perf_current_no_answer_stage' => ['deals' => $dayDeals, 'title' => 'تماس عدم پاسخ'],
            'activity_perf_current_payment_link_stage' => ['deals' => $dayDeals, 'title' => 'ارسال لینک پرداخت'],
            'activity_perf_current_followup_call_stage' => ['deals' => $dayDeals, 'title' => 'تماس پیگیری'],
        ];

        $metricActivityMap = [
            'activity_perf_past_no_answer' => 'تماس عدم پاسخ',
            'activity_perf_past_outstanding_followup' => 'پیگیری مانده حساب',
            'activity_perf_past_payment_link' => 'ارسال لینک پرداخت',
            'activity_perf_past_followup_call' => 'تماس پیگیری',
            'activity_perf_current_no_answer' => 'تماس عدم پاسخ',
            'activity_perf_current_payment_link' => 'ارسال لینک پرداخت',
            'activity_perf_current_followup_call' => 'تماس پیگیری',
        ];
        $metrics = [];
        $drilldown = [];
        foreach (array_keys($metricActivityMap) as $metricKey) {
            $metrics[$metricKey] = ['duration_minutes' => 0];
            $drilldown[$metricKey] = ['activities' => [], 'deal_ids' => []];
        }
        foreach ($stageMetricMap as $metricKey => $_cfg) {
            $metrics[$metricKey] = ['count' => 0];
            $drilldown[$metricKey] = ['deal_ids' => []];
        }
        $drilldown['activity_perf_past_outstanding_deals'] = [
            'deal_ids' => array_values($pastOutstandingPaidTodayDealIds),
            'ceo_payment_rows' => $balanceDepositPaymentRows,
        ];
        $drilldown['activity_perf_past_no_contact'] = ['deal_ids' => $pastNoContactDealIds];
        $drilldown['activity_perf_current_registered_deals'] = ['deal_ids' => $dayDealIds];
        $drilldown['activity_perf_current_no_contact'] = ['deal_ids' => $dayNoContactDealIds];
        $drilldown['activity_perf_past_outstanding_amount'] = [
            'deal_ids' => array_values($pastOutstandingPaidTodayDealIds),
            'ceo_payment_rows' => $balanceDepositPaymentRows,
        ];
        $metrics['activity_perf_past_outstanding_deals'] = [
            'amount' => $pastOutstandingAmount,
            'count' => count($pastOutstandingDealIds),
            'paid_today_count' => count($pastOutstandingPaidTodayDealIds),
        ];
        $metrics['activity_perf_past_outstanding_amount'] = [
            'amount' => $pastOutstandingAmount,
            'paid_today_amount' => $oldOutstandingPaymentAmount,
        ];

        foreach ($stageMetricMap as $metricKey => $cfg) {
            $targetTitle = $cfg['title'];
            foreach (($cfg['deals'] ?? []) as $dealRow) {
                $dealId = (int)($dealRow['id'] ?? 0);
                if ($dealId <= 0) {
                    continue;
                }
                $stageTitle = $this->normalizeActivityTypeLabelPhp((string)($dealRow['pipeline_stage'] ?? ''));
                if ($stageTitle !== $targetTitle) {
                    continue;
                }
                $drilldown[$metricKey]['deal_ids'][$dealId] = $dealId;
            }
            $drilldown[$metricKey]['deal_ids'] = array_values(array_unique(array_map('intval', $drilldown[$metricKey]['deal_ids'] ?? [])));
            $metrics[$metricKey]['count'] = count($drilldown[$metricKey]['deal_ids']);
        }

        foreach ($activityRows as $activityRow) {
            $dealId = (int)($activityRow['deal_id'] ?? 0);
            $dealDate = trim((string)($effectiveDealDateById[$dealId] ?? $activityRow['deal_date'] ?? ''));
            $activityType = trim((string)($activityRow['activity_type'] ?? ''));
            $duration = (int)($activityRow['activity_duration_minutes'] ?? 0);
            $isPast = ($dealDate !== '' && $dealDate < $dateFrom);
            $isCurrent = ($dealDate !== '' && $dealDate >= $dateFrom && $dealDate <= $dateTo);
            if (!$isPast && !$isCurrent) {
                continue;
            }

            $metricKey = null;
            if ($activityType === 'تماس عدم پاسخ') {
                $metricKey = $isPast ? 'activity_perf_past_no_answer' : 'activity_perf_current_no_answer';
            } elseif ($activityType === 'پیگیری مانده حساب' && $isPast) {
                $metricKey = 'activity_perf_past_outstanding_followup';
            } elseif ($activityType === 'ارسال لینک پرداخت') {
                $metricKey = $isPast ? 'activity_perf_past_payment_link' : 'activity_perf_current_payment_link';
            } elseif ($activityType === 'تماس پیگیری') {
                $metricKey = $isPast ? 'activity_perf_past_followup_call' : 'activity_perf_current_followup_call';
            }

            if ($metricKey === null) {
                continue;
            }
            $metrics[$metricKey]['duration_minutes'] = (int)($metrics[$metricKey]['duration_minutes'] ?? 0) + max(0, $duration);
            $drilldown[$metricKey]['activities'][] = $activityRow;
            $dealId = (int)($activityRow['deal_id'] ?? 0);
            if ($dealId > 0) {
                $drilldown[$metricKey]['deal_ids'][$dealId] = $dealId;
            }
        }

        $activityStageMetricMap = [
            'activity_perf_past_no_answer' => 'activity_perf_past_no_answer_stage',
            'activity_perf_past_outstanding_followup' => 'activity_perf_past_outstanding_followup_stage',
            'activity_perf_past_payment_link' => 'activity_perf_past_payment_link_stage',
            'activity_perf_past_followup_call' => 'activity_perf_past_followup_call_stage',
            'activity_perf_current_no_answer' => 'activity_perf_current_no_answer_stage',
            'activity_perf_current_payment_link' => 'activity_perf_current_payment_link_stage',
            'activity_perf_current_followup_call' => 'activity_perf_current_followup_call_stage',
        ];
        foreach ($activityStageMetricMap as $activityMetricKey => $stageMetricKey) {
            $activityDealIds = $drilldown[$activityMetricKey]['deal_ids'] ?? [];
            $stageDealIds = $drilldown[$stageMetricKey]['deal_ids'] ?? [];
            $drilldown[$activityMetricKey]['activity_deal_ids'] = array_values(array_unique(array_map('intval', $activityDealIds)));
            $drilldown[$activityMetricKey]['deal_ids'] = array_values(array_unique(array_map('intval', $stageDealIds)));
        }

        foreach ($drilldown as $metricKey => $payload) {
            if (isset($payload['deal_ids']) && is_array($payload['deal_ids'])) {
                $drilldown[$metricKey]['deal_ids'] = array_values(array_unique(array_map('intval', $payload['deal_ids'])));
            }
        }

        return [
            'metrics' => $metrics,
            'drilldown' => $drilldown,
        ];
    }

    private function getActivityPerformanceCardDrilldown(array $filters, string $metricKey, string $rowOwnerDidarId = ''): array
    {
        $state = $this->buildActivityPerformanceState($filters, $rowOwnerDidarId);
        $payload = $state['drilldown'][$metricKey] ?? null;
        if (!is_array($payload)) {
            return ['leads' => [], 'deals' => [], 'activities' => [], 'payments' => [], 'meta' => ['total_leads' => 0, 'total_deals' => 0, 'total_activities' => 0, 'total_payments' => 0]];
        }

        $dealIds = array_values(array_unique(array_map('intval', $payload['deal_ids'] ?? [])));
        if (!empty($payload['ceo_payment_rows'])) {
            $base = $this->buildCeoPaymentDrilldown(array_values($payload['ceo_payment_rows']), $filters);
            if (!empty($dealIds)) {
                $base['meta']['activity_deal_ids'] = $dealIds;
            }
            if (!empty($payload['activities'])) {
                $base['activities'] = array_values($payload['activities']);
                $base['meta']['total_activities'] = count($base['activities']);
            }
            return $base;
        }
        if (empty($dealIds) && !empty($payload['payments'])) {
            $dealIds = array_values(array_unique(array_map(static function ($row) {
                return (int)($row['deal_id'] ?? 0);
            }, $payload['payments'])));
        }
        $base = $this->buildSimpleDealLeadDrilldownByIds($dealIds, $filters);
        if (!empty($payload['payments'])) {
            $base['payments'] = array_values($payload['payments']);
            $base['meta']['total_payments'] = count($base['payments']);
        }
        if (!empty($payload['activities'])) {
            $base['activities'] = array_values($payload['activities']);
            $base['meta']['total_activities'] = count($base['activities']);
        }
        return $base;
    }

    private function getPerformancePartitionCardDrilldown(array $filters, string $mode, string $rowOwnerDidarId = ''): array
    {
        if ($rowOwnerDidarId !== '') {
            $filters['owner_didar_id'] = $rowOwnerDidarId;
            $filters['owner_didar_ids_csv'] = $rowOwnerDidarId;
            $filters['owner_didar_ids'] = [$rowOwnerDidarId];
        }
        if ($this->getKpiPaymentDateBasis($filters, 'sales') !== 'ui_payment_date') {
            if ($mode === 'open_valid') {
                return $this->getDealBasedDrilldown($filters, 'deal_status', 'deal_pending_total', '', $rowOwnerDidarId, '', '');
            }
            if ($mode === 'won_valid') {
                return $this->getDealBasedDrilldown($filters, 'deal_status', 'deal_won_total', '', $rowOwnerDidarId, '', '');
            }
            if ($mode === 'lost_valid') {
                return $this->getDealBasedDrilldown($filters, 'deal_status', 'deal_lost_total', '', $rowOwnerDidarId, '', '');
            }
            return ['leads' => [], 'deals' => [], 'activities' => [], 'payments' => [], 'meta' => ['total_leads' => 0, 'total_deals' => 0, 'total_activities' => 0, 'total_payments' => 0]];
        }

        $oracle = $this->getUiOracleKpiData($filters);
        $setKey = 'partition_open_valid';
        if ($mode === 'lost_valid') {
            $setKey = 'partition_lost_valid';
        } elseif ($mode === 'won_valid') {
            $setKey = 'partition_won_valid';
        }
        $ids = array_map('intval', array_keys($oracle['sets'][$setKey] ?? []));
        return $this->buildSimpleDealLeadDrilldownByIds($ids, $filters);
    }

    private function getDealConversionAnalysisDrilldown(array $filters, string $rowOwnerDidarId = ''): array
    {
        if ($rowOwnerDidarId !== '') {
            $filters['owner_didar_id'] = $rowOwnerDidarId;
            $filters['owner_didar_ids_csv'] = $rowOwnerDidarId;
            $filters['owner_didar_ids'] = [$rowOwnerDidarId];
        }

        $state = $this->buildUiOracleState($filters);
        $cohortIds = $this->uiOracleDealCohortIds($state, $filters);
        $salesBasis = $this->getKpiPaymentDateBasis($filters, 'sales');
        $salesRule = $this->getKpiPaymentCountRule($filters, 'sales');
        $statusScope = $this->getKpiPaymentStatusScope($filters);

        $salesWindowAgg = $this->uiOracleAggregateByDeal(
            $this->uiOraclePaymentEvents($state, $salesBasis, $statusScope, true, $filters, 'sales'),
            $salesRule
        );
        $firstAllAgg = $this->uiOracleAggregateByDeal(
            $this->uiOraclePaymentEvents($state, $salesBasis, $statusScope, false, $filters, 'sales'),
            'first_payment_only'
        );

        $oracle = $this->getUiOracleKpiData($filters);
        $referenceIds = array_map('intval', array_keys($oracle['sets']['conversion_reference'] ?? []));
        $convertedReferenceIds = array_map('intval', array_keys($oracle['sets']['sales_card_qualified'] ?? []));
        $convertedReferenceLookup = array_fill_keys($convertedReferenceIds, true);
        $validIds = array_fill_keys(array_map('strval', $referenceIds), true);
        $convertedIds = array_fill_keys(array_map('strval', $convertedReferenceIds), true);
        $excludedRows = [];
        $notConvertedRows = [];
        $referenceRows = [];
        foreach ($referenceIds as $dealId) {
            $deal = $state['deals'][(string)$dealId] ?? null;
            if (!$deal) {
                continue;
            }
            $windowPaid = (float)($salesWindowAgg[(string)$dealId]['total'] ?? 0);
            $allFirstDate = (string)($firstAllAgg[(string)$dealId]['first'] ?? '');
            $rawStatus = (string)($deal['status'] ?? '');
            $reportingStatusGroup = $rawStatus === 'Lost' ? 'lost' : (($windowPaid > 0 || isset($convertedReferenceLookup[(int)$dealId])) ? 'won' : 'pending');
            $referenceRows[] = [
                'deal_id' => (int)$dealId,
                'didar_deal_id' => (string)($deal['didar_deal_id'] ?? ''),
                'title' => (string)($deal['title'] ?? ('#' . $dealId)),
                'deal_date' => (string)($deal['deal_date'] ?? ''),
                'first_payment_date' => $allFirstDate,
                'deal_status_group' => $reportingStatusGroup,
                'payable_amount' => (float)($deal['payable_amount'] ?? 0),
                'total_paid' => $windowPaid,
                'owner_didar_id' => (string)($deal['owner_didar_id'] ?? ''),
                'lead_type' => $this->uiOracleLeadType($deal, $state),
                'is_converted' => isset($convertedReferenceLookup[(int)$dealId]) ? 1 : 0,
                'conversion_reason_label' => isset($convertedReferenceLookup[(int)$dealId])
                    ? 'طبق معیار فروش فعال، در بازه فروش/واریزی دارد'
                    : 'در مرجع نرخ تبدیل قرار دارد اما در بازه و با معیار فروش فعال، تبدیل نشده است',
            ];
            if (!isset($convertedReferenceLookup[(int)$dealId])) {
                $notConvertedRows[] = $referenceRows[count($referenceRows) - 1] + [
                    'exclude_reason_key' => 'no_sale_in_selected_window',
                    'exclude_reason_label' => 'در بازه و با معیار فروش فعال، واریزی/فروش ندارد',
                ];
            }
        }
        $includedDealIds = array_map(static function ($id) { return (int)$id; }, $referenceIds);
        $includedDrilldown = $this->buildSimpleDealLeadDrilldownByIds($includedDealIds, $filters);

        $reasonBuckets = [];
        foreach ($excludedRows as $row) {
            $k = (string)($row['exclude_reason_key'] ?? 'unknown');
            if (!isset($reasonBuckets[$k])) {
                $reasonBuckets[$k] = ['key' => $k, 'label' => (string)($row['exclude_reason_label'] ?? 'نامشخص'), 'count' => 0];
            }
            $reasonBuckets[$k]['count']++;
        }
        foreach ($notConvertedRows as $row) {
            $k = (string)($row['exclude_reason_key'] ?? 'unknown');
            if (!isset($reasonBuckets[$k])) {
                $reasonBuckets[$k] = ['key' => $k, 'label' => (string)($row['exclude_reason_label'] ?? 'نامشخص'), 'count' => 0];
            }
            $reasonBuckets[$k]['count']++;
        }
        $reasonBuckets = array_values($reasonBuckets);
        usort($reasonBuckets, static function ($a, $b) {
            return ((int)$b['count'] <=> (int)$a['count']);
        });

        $validCount = count($validIds);
        $convertedCount = count($convertedIds);
        $conversionRate = $validCount > 0 ? round(($convertedCount / $validCount) * 100, 2) : 0.0;
        $nonConversionRate = $validCount > 0 ? round((($validCount - $convertedCount) / $validCount) * 100, 2) : 0.0;
        $crossDateAddedIds = array_map('intval', array_keys($oracle['sets']['conversion_cross_date_sales_added'] ?? []));

        return [
            'leads' => $includedDrilldown['leads'] ?? [],
            'deals' => $includedDrilldown['deals'] ?? [],
            'activities' => [],
            'payments' => [],
            'meta' => [
                'drilldown_kind' => 'conversion_analysis',
                'conversion_type' => 'deal',
                'title' => 'جزئیات نرخ تبدیل معاملات',
                'total_leads' => (int)count($includedDrilldown['leads'] ?? []),
                'total_deals' => (int)count($includedDrilldown['deals'] ?? []),
                'total_activities' => 0,
                'total_payments' => 0,
            ],
            'conversion_analysis' => [
                'type' => 'deal',
                'formula_title' => 'فرمول نرخ تبدیل معاملات',
                'formula_text' => 'نرخ تبدیل = تعداد معاملات تبدیل‌شده ÷ معاملات مرتبط با بازه (ثبت‌شده در بازه یا ثبت خارج از بازه با اولین واریزی در بازه) × ۱۰۰',
                'criteria' => $this->buildConversionCriteriaSummary($filters),
                'summary' => [
                    'raw_cohort_count' => count($referenceIds),
                    'excluded_count' => count($excludedRows),
                    'valid_cohort_count' => (int)count($referenceIds),
                    'converted_count' => (int)count($convertedReferenceIds),
                    'not_converted_count' => max(0, count($referenceIds) - count($convertedReferenceIds)),
                    'conversion_rate' => (count($referenceIds) > 0 ? round((count($convertedReferenceIds) / count($referenceIds)) * 100, 2) : 0.0),
                    'non_conversion_rate' => (count($referenceIds) > 0 ? round(((count($referenceIds) - count($convertedReferenceIds)) / count($referenceIds)) * 100, 2) : 0.0),
                ],
                'option_a_partition' => [
                    'formula_text' => 'مرجع نرخ تبدیل = معاملات ثبت‌شده در بازه + معاملات ثبت خارج از بازه که اولین واریزی فروش‌شان در بازه است، با اجتماع {فروش کل، بدون پرداخت، ناموفق، موفق مالی}',
                    'sales_count' => (int)($oracle['sales_card']['deal_count'] ?? 0),
                    'without_payment_count' => (int)($oracle['conversion_partition']['without_payment_count'] ?? 0),
                    'lost_count' => (int)($oracle['conversion_partition']['lost_count'] ?? 0),
                    'won_count' => (int)($oracle['conversion_partition']['won_count'] ?? 0),
                    'reference_total' => (int)($oracle['deal_conversion']['total_deals'] ?? 0),
                    'sales_won_overlap_count' => (int)count($oracle['sets']['conversion_sales_won_overlap'] ?? []),
                    'sales_won_overlap_ids' => array_slice(array_map('intval', array_keys($oracle['sets']['conversion_sales_won_overlap'] ?? [])), 0, 200),
                    'cross_date_sales_added_count' => count($crossDateAddedIds),
                    'cross_date_sales_added_ids' => array_slice($crossDateAddedIds, 0, 200),
                ],
                'excluded_reasons_summary' => $reasonBuckets,
                'included_deals' => $referenceRows,
                'excluded_deals' => array_merge($excludedRows, $notConvertedRows),
                'debug' => [
                    'sales_date_basis' => $salesBasis,
                    'sales_count_rule' => $salesRule,
                    'payment_status_scope' => $statusScope,
                ],
            ],
        ];
    }

    private function getLeadConversionLeadAnalysisDrilldown(array $filters, string $rowOwnerDidarId = '', bool $includeFullCohort = true): array
    {
        if ($rowOwnerDidarId !== '') {
            $filters['owner_didar_id'] = $rowOwnerDidarId;
            $filters['owner_didar_ids_csv'] = $rowOwnerDidarId;
            $filters['owner_didar_ids'] = [$rowOwnerDidarId];
        }
        $pdo = $this->db->getPdo();
        $paymentsByDealSql = $this->buildKpiPaymentsByDealSql($filters, 'sales');
        $dateFrom = $filters['date_from'] ?? null;
        $dateTo = $filters['date_to'] ?? null;

        $personWhereParts = [];
        $personParams = [];
        if (!empty($dateFrom)) { $personWhereParts[] = "DATE(p.register_time) >= ?"; $personParams[] = $dateFrom; }
        if (!empty($dateTo)) { $personWhereParts[] = "DATE(p.register_time) <= ?"; $personParams[] = $dateTo; }
        if (!empty($filters['owner_didar_ids_csv'])) { $personWhereParts[] = "FIND_IN_SET(p.owner_didar_id, ?)"; $personParams[] = $filters['owner_didar_ids_csv']; }
        elseif (!empty($filters['owner_didar_id'])) { $personWhereParts[] = "p.owner_didar_id = ?"; $personParams[] = $filters['owner_didar_id']; }
        $personWhere = !empty($personWhereParts) ? implode(' AND ', $personWhereParts) : '1=1';

        $leadsSql = "
            SELECT
                p.id as person_id,
                p.didar_contact_id as contact_id,
                TRIM(CONCAT(COALESCE(p.first_name, ''), ' ', COALESCE(p.last_name, ''))) as full_name,
                p.mobile_phone,
                p.secondary_mobile_phone,
                DATE(COALESCE(p.register_time, p.created_at)) as lead_date,
                p.owner_didar_id,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name
            FROM persons p
            LEFT JOIN users u ON u.didar_user_id = p.owner_didar_id
            WHERE {$personWhere}
            ORDER BY lead_date DESC, p.id DESC
        ";
        $allLeads = $this->runSelectAll($leadsSql, $personParams);
        if (empty($allLeads)) {
            return [
                'leads' => [], 'deals' => [], 'activities' => [], 'payments' => [],
                'meta' => ['drilldown_kind' => 'conversion_analysis', 'conversion_type' => 'lead', 'title' => 'جزئیات نرخ تبدیل لید جدید', 'total_leads' => 0, 'total_deals' => 0, 'total_activities' => 0, 'total_payments' => 0],
                'conversion_analysis' => [
                    'type' => 'lead',
                    'formula_title' => 'فرمول نرخ تبدیل لید جدید',
                    'formula_text' => 'نرخ تبدیل لید جدید = لیدهای جدیدِ تبدیل‌شده ÷ کل لیدهای جدید × ۱۰۰',
                    'criteria' => $this->buildConversionCriteriaSummary($filters),
                    'summary' => ['raw_cohort_count' => 0, 'excluded_count' => 0, 'valid_cohort_count' => 0, 'converted_count' => 0, 'not_converted_count' => 0, 'conversion_rate' => 0, 'non_conversion_rate' => 0],
                    'excluded_reasons_summary' => [],
                    'included_leads' => [],
                    'excluded_leads' => [],
                ]
            ];
        }

        $contactIds = array_values(array_filter(array_map(static function ($r) { return trim((string)($r['contact_id'] ?? '')); }, $allLeads)));
        $dealsByContact = [];
        $convertedLeadIds = [];
        if (!empty($contactIds)) {
            $ph = implode(',', array_fill(0, count($contactIds), '?'));
            $dealsJoinSql = "
                SELECT
                    d.id as deal_id,
                    d.didar_deal_id,
                    d.contact_didar_id as contact_id,
                    d.title,
                    DATE(COALESCE(d.register_time, d.created_at)) as deal_date,
                    d.owner_didar_id,
                    COALESCE(u.display_name, 'بدون مسئول') as owner_name,
                    COALESCE(d.payable_amount, 0) as payable_amount,
                    COALESCE(tp.total_paid, 0) as total_paid
                FROM deals d
                LEFT JOIN users u ON u.didar_user_id = d.owner_didar_id
                LEFT JOIN ({$paymentsByDealSql}) tp ON tp.deal_id = d.id
                WHERE d.contact_didar_id IN ({$ph})
                ORDER BY d.id DESC
            ";
            $dealRows = $this->runSelectAll($dealsJoinSql, $contactIds);
            foreach ($dealRows as $row) {
                $cid = (string)($row['contact_id'] ?? '');
                if ($cid === '') continue;
                $dealsByContact[$cid][] = $row;
                if ((float)($row['total_paid'] ?? 0) > 0) {
                    $convertedLeadIds[$cid] = true;
                }
            }
        }

        $includedLeads = [];
        $excludedLeads = [];
        $reasonBuckets = [];
        foreach ($allLeads as $lead) {
            $cid = (string)($lead['contact_id'] ?? '');
            $leadDeals = $dealsByContact[$cid] ?? [];
            $hasAnyDeal = !empty($leadDeals);
            $hasPaidDeal = isset($convertedLeadIds[$cid]);
            $row = [
                'contact_id' => $cid,
                'full_name' => (string)($lead['full_name'] ?? ''),
                'mobile_phone' => (string)($lead['mobile_phone'] ?? ''),
                'secondary_mobile_phone' => (string)($lead['secondary_mobile_phone'] ?? ''),
                'lead_date' => (string)($lead['lead_date'] ?? ''),
                'owner_name' => (string)($lead['owner_name'] ?? ''),
                'owner_didar_id' => (string)($lead['owner_didar_id'] ?? ''),
                'deal_count' => count($leadDeals),
                'converted_deal_count' => 0,
                'first_deal_date' => '',
                'first_payment_date' => '',
            ];
            if ($hasAnyDeal) {
                $firstDealDate = '';
                $firstPaymentDate = '';
                $convertedDealCount = 0;
                foreach ($leadDeals as $dealRow) {
                    $dd = (string)($dealRow['deal_date'] ?? '');
                    if ($dd !== '' && ($firstDealDate === '' || $dd < $firstDealDate)) $firstDealDate = $dd;
                    if ((float)($dealRow['total_paid'] ?? 0) > 0) {
                        $convertedDealCount++;
                    }
                }
                $row['converted_deal_count'] = $convertedDealCount;
                $row['first_deal_date'] = $firstDealDate;
                // approximate first payment date from tx/json is not readily available per lead; keep empty in phase 1
                $row['first_payment_date'] = $firstPaymentDate;
            }

            if ($hasPaidDeal) {
                $row['is_converted'] = 1;
                $row['conversion_reason_label'] = 'حداقل یک معامله با پرداخت (طبق معیار فروش فعال) دارد';
                $includedLeads[] = $row;
            } else {
                $row['is_converted'] = 0;
                if (!$hasAnyDeal) {
                    $row['exclude_reason_key'] = 'no_deal';
                    $row['exclude_reason_label'] = 'هیچ معامله‌ای برای لید ثبت نشده است';
                } else {
                    $row['exclude_reason_key'] = 'deals_without_paid';
                    $row['exclude_reason_label'] = 'معامله دارد اما هیچ معامله‌ای با پرداختِ منطبق با معیار فعال ندارد';
                }
                $excludedLeads[] = $row;
                $k = (string)$row['exclude_reason_key'];
                if (!isset($reasonBuckets[$k])) {
                    $reasonBuckets[$k] = ['key' => $k, 'label' => (string)$row['exclude_reason_label'], 'count' => 0];
                }
                $reasonBuckets[$k]['count']++;
            }
        }
        $reasonBuckets = array_values($reasonBuckets);
        usort($reasonBuckets, static function ($a, $b) { return ((int)$b['count'] <=> (int)$a['count']); });

        $includedContactIds = array_values(array_map(static function ($r) { return (string)$r['contact_id']; }, $includedLeads));
        $allLeadRows = $includeFullCohort ? array_merge($includedLeads, $excludedLeads) : $includedLeads;
        usort($allLeadRows, static function ($a, $b) {
            $dateCmp = strcmp((string)($b['lead_date'] ?? ''), (string)($a['lead_date'] ?? ''));
            if ($dateCmp !== 0) {
                return $dateCmp;
            }
            return strcmp((string)($a['full_name'] ?? ''), (string)($b['full_name'] ?? ''));
        });

        $includedDeals = [];
        foreach ($includedContactIds as $cid) {
            foreach (($dealsByContact[$cid] ?? []) as $dealRow) {
                if ((float)($dealRow['total_paid'] ?? 0) > 0) {
                    $includedDeals[] = [
                        'deal_id' => (int)($dealRow['deal_id'] ?? 0),
                        'didar_deal_id' => (string)($dealRow['didar_deal_id'] ?? ''),
                        'title' => (string)($dealRow['title'] ?? ''),
                        'deal_date' => (string)($dealRow['deal_date'] ?? ''),
                        'owner_name' => (string)($dealRow['owner_name'] ?? ''),
                        'owner_didar_id' => (string)($dealRow['owner_didar_id'] ?? ''),
                        'contact_didar_id' => (string)($dealRow['contact_id'] ?? ''),
                        'deal_status_group' => 'pending',
                        'payable_amount' => (float)($dealRow['payable_amount'] ?? 0),
                        'total_paid' => (float)($dealRow['total_paid'] ?? 0),
                        'has_payment' => ((float)($dealRow['total_paid'] ?? 0) > 0 ? 1 : 0),
                    ];
                }
            }
        }

        $totalNewLeads = count($allLeads);
        $convertedLeads = count($includedLeads);
        $conversionRate = $totalNewLeads > 0 ? round(($convertedLeads / $totalNewLeads) * 100, 2) : 0.0;
        $nonConversionRate = $totalNewLeads > 0 ? round((($totalNewLeads - $convertedLeads) / $totalNewLeads) * 100, 2) : 0.0;

        return [
            'leads' => $allLeadRows,
            'deals' => $includedDeals,
            'activities' => [],
            'payments' => [],
            'meta' => [
                'drilldown_kind' => 'conversion_analysis',
                'conversion_type' => 'lead',
                'title' => 'جزئیات نرخ تبدیل لید جدید',
                'total_leads' => $includeFullCohort ? $totalNewLeads : count($includedLeads),
                'total_deals' => count($includedDeals),
                'total_activities' => 0,
                'total_payments' => 0,
            ],
            'conversion_analysis' => [
                'type' => 'lead',
                'formula_title' => 'فرمول نرخ تبدیل لید جدید',
                'formula_text' => 'نرخ تبدیل لید جدید = تعداد لیدهای جدیدِ تبدیل‌شده ÷ کل لیدهای جدید در بازه × ۱۰۰',
                'criteria' => $this->buildConversionCriteriaSummary($filters),
                'summary' => [
                    'raw_cohort_count' => $totalNewLeads,
                    'excluded_count' => count($excludedLeads),
                    'valid_cohort_count' => $totalNewLeads,
                    'converted_count' => $convertedLeads,
                    'not_converted_count' => max(0, $totalNewLeads - $convertedLeads),
                    'conversion_rate' => $conversionRate,
                    'non_conversion_rate' => $nonConversionRate,
                ],
                'excluded_reasons_summary' => $reasonBuckets,
                'included_leads' => $includedLeads,
                'excluded_leads' => $excludedLeads,
            ],
        ];
    }

    private function buildConversionCriteriaSummary(array $filters): array
    {
        return [
            'date_from' => (string)($filters['date_from'] ?? ''),
            'date_to' => (string)($filters['date_to'] ?? ''),
            'sales_date_basis' => (string)$this->getKpiPaymentDateBasis($filters, 'sales'),
            'sales_count_rule' => (string)$this->getKpiPaymentCountRule($filters, 'sales'),
            'payment_date_basis' => (string)$this->getKpiPaymentDateBasis($filters, 'payment'),
            'payment_count_rule' => (string)$this->getKpiPaymentCountRule($filters, 'payment'),
            'payment_status_scope' => (string)$this->getKpiPaymentStatusScope($filters),
            'legacy_deal_scope' => (string)($filters['legacy_deal_scope'] ?? 'exclude'),
            'owner_didar_ids_csv' => (string)($filters['owner_didar_ids_csv'] ?? ''),
            'owner_didar_id' => (string)($filters['owner_didar_id'] ?? ''),
        ];
    }

    private function getSalesTotalCardDrilldown(array $filters, string $rowOwnerDidarId): array
    {
        if ($this->getKpiPaymentDateBasis($filters, 'sales') === 'ui_payment_date') {
            return $this->getSalesTotalCardDrilldownUiOracle($filters, $rowOwnerDidarId);
        }
        $whereDeals = $this->buildWhereClauseForDealsNoDate($filters, 'd');
        $paramsDeals = $this->buildParamsForDealsNoDate($filters);
        if ($rowOwnerDidarId !== '') {
            $whereDeals .= " AND d.owner_didar_id = ?";
            $paramsDeals[] = $rowOwnerDidarId;
        }

        $salesQualifierSql = $this->buildKpiFirstPaymentDayByDealSql($filters, 'sales');
        $salesAllTimeTotalsSql = $this->buildKpiAllTimePaymentTotalsByDealSql($filters, 'sales');
        $legacyOpenClause = $this->buildLegacyDealFilterClause($filters, 'd2');
        $legacyOpenClause = $legacyOpenClause !== '' ? (' AND ' . $legacyOpenClause) : '';
        $failureReasonExpr = $this->buildFailureReasonExpr('d');

        $paymentDateWhere = [];
        $paymentDateParams = [];
        if (!empty($filters['date_from'])) {
            $paymentDateWhere[] = "pbd.payment_date >= ?";
            $paymentDateParams[] = $filters['date_from'];
        }
        if (!empty($filters['date_to'])) {
            $paymentDateWhere[] = "pbd.payment_date <= ?";
            $paymentDateParams[] = $filters['date_to'];
        }
        $paymentDateClause = !empty($paymentDateWhere) ? implode(' AND ', $paymentDateWhere) : '1=1';
        $paymentDateClauseQ = str_replace('pbd.', 'q.', $paymentDateClause);

        $dealsBaseSql = "
            SELECT
                d.id as deal_id,
                d.didar_deal_id,
                d.title,
                d.status,
                d.pipeline_stage,
                COALESCE(d.acquaintance_source, '') as acquaintance_source,
                COALESCE(d.acquaintance_detail, '') as acquaintance_detail,
                COALESCE(d.content_topic, '') as content_topic,
                COALESCE(d.created_by_name, '') as created_by_name,
                COALESCE(d.created_via, '') as created_via,
                DATE(COALESCE(d.register_time, d.created_at)) as deal_date,
                COALESCE(d.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name,
                COALESCE(d.contact_didar_id, '') as contact_didar_id,
                COALESCE(p.sale_status, '') as lead_sale_status,
                DATE(COALESCE(p.register_time, p.created_at)) as lead_register_time,
                COALESCE(p.register_time_jalali, '') as lead_register_time_jalali,
                {$this->buildDealProductHintExpr('d')} as deal_product_hint,
                CASE
                    WHEN DATE(COALESCE(p.register_time, p.created_at)) = DATE(COALESCE(d.register_time, d.created_at)) THEN 'new'
                    ELSE 'old'
                END as lead_type,
                CASE
                    WHEN d.status = 'Won' THEN 'won'
                    WHEN d.status = 'Lost' THEN 'lost'
                    ELSE 'pending'
                END as deal_status_group,
                COALESCE(d.payable_amount, 0) as payable_amount,
                COALESCE(pay.total_paid, 0) as total_paid,
                pay.first_payment_date,
                DATE(COALESCE(d.lost_time, d.created_at)) as lost_date,
                CASE WHEN COALESCE(pay.total_paid, 0) > 0 THEN 1 ELSE 0 END as has_payment,
                CASE WHEN COALESCE(od.open_count, 0) > 0 THEN 1 ELSE 0 END as has_open_deal,
                {$failureReasonExpr} as failure_reason
            FROM deals d
            INNER JOIN (
                SELECT DISTINCT q.deal_id, q.payment_date as first_payment_date
                FROM ({$salesQualifierSql}) q
                WHERE {$paymentDateClauseQ}
            ) pw ON pw.deal_id = d.id
            LEFT JOIN users u ON u.didar_user_id = d.owner_didar_id
            LEFT JOIN persons p ON p.didar_contact_id = d.contact_didar_id
            LEFT JOIN ({$salesAllTimeTotalsSql}) pay ON pay.deal_id = d.id
            LEFT JOIN (
                SELECT
                    d2.contact_didar_id,
                    COUNT(*) as open_count
                FROM deals d2
                WHERE d2.status NOT IN ('Won', 'Lost')
                  {$legacyOpenClause}
                GROUP BY d2.contact_didar_id
            ) od ON od.contact_didar_id = d.contact_didar_id
            WHERE {$whereDeals}
              AND pw.first_payment_date >= DATE(COALESCE(d.register_time, d.created_at))
        ";

        $params = array_merge($paymentDateParams, $paramsDeals);

        $dealsSql = "
            SELECT DISTINCT
                b.deal_id,
                b.didar_deal_id,
                b.title,
                b.status,
                b.pipeline_stage,
                b.acquaintance_source,
                b.acquaintance_detail,
                b.content_topic,
                b.created_by_name,
                b.created_via,
                b.deal_date,
                b.owner_name,
                b.owner_didar_id,
                b.contact_didar_id,
                b.lead_sale_status,
                b.lead_register_time,
                b.lead_register_time_jalali,
                b.deal_status_group,
                b.payable_amount,
                b.total_paid,
                b.has_payment
            FROM ({$dealsBaseSql}) b
            ORDER BY b.deal_date DESC, b.deal_id DESC
        ";
        $deals = $this->runSelectAll($dealsSql, $params);

        $leadsSql = "
            SELECT DISTINCT
                p.didar_contact_id as contact_id,
                TRIM(CONCAT(COALESCE(p.first_name, ''), ' ', COALESCE(p.last_name, ''))) as full_name,
                p.mobile_phone,
                p.secondary_mobile_phone,
                COALESCE(p.acquaintance_source, '') as acquaintance_source,
                COALESCE(p.acquaintance_detail, '') as acquaintance_detail,
                COALESCE(p.content_topic, '') as content_topic,
                DATE(COALESCE(p.register_time, p.created_at)) as lead_date,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name
            FROM persons p
            LEFT JOIN users u ON u.didar_user_id = p.owner_didar_id
            INNER JOIN (
                SELECT DISTINCT contact_didar_id
                FROM ({$dealsBaseSql}) m
                WHERE COALESCE(m.contact_didar_id, '') <> ''
            ) md ON md.contact_didar_id = p.didar_contact_id
            ORDER BY lead_date DESC, full_name ASC
        ";
        $leads = $this->runSelectAll($leadsSql, $params);

        return [
            'leads' => $leads,
            'deals' => $deals,
            'activities' => [],
            'payments' => [],
            'meta' => [
                'total_leads' => count($leads),
                'total_deals' => count($deals),
                'total_activities' => 0,
                'total_payments' => 0
            ]
        ];
    }

    private function getDailySalesChartPointDrilldown(array $filters, string $rowDate): array
    {
        if ($rowDate === '') {
            return ['leads' => [], 'deals' => [], 'activities' => [], 'payments' => [], 'meta' => ['total_leads' => 0, 'total_deals' => 0, 'total_activities' => 0, 'total_payments' => 0]];
        }

        if ($this->getKpiPaymentDateBasis($filters, 'sales') === 'ui_payment_date') {
            $dailyFilters = $filters;
            $dailyFilters['date_from'] = $rowDate;
            $dailyFilters['date_to'] = $rowDate;
            $dailyFilters['person_date_from'] = $rowDate;
            $dailyFilters['person_date_to'] = $rowDate;

            $oracle = $this->getUiOracleKpiData($filters);
            $dealIds = [];
            $dayPaidMap = [];
            foreach (($oracle['sets']['sales_card_qualified'] ?? []) as $dealId => $_) {
                $firstDate = (string)($oracle['aggs']['sales_first_all'][(string)$dealId]['first'] ?? '');
                if ($firstDate !== $rowDate) {
                    continue;
                }
                $id = (int)$dealId;
                if ($id <= 0) {
                    continue;
                }
                $dealIds[] = $id;
                $dayPaidMap[(string)$id] = (float)($oracle['aggs']['sales_window'][(string)$dealId]['total'] ?? 0);
            }
            if (empty($dealIds)) {
                return ['leads' => [], 'deals' => [], 'activities' => [], 'payments' => [], 'meta' => ['total_leads' => 0, 'total_deals' => 0, 'total_activities' => 0, 'total_payments' => 0]];
            }

            $result = $this->buildSimpleDealLeadDrilldownByIds($dealIds, $dailyFilters, 'sales');
            foreach ($result['deals'] as &$deal) {
                $did = (string)($deal['deal_id'] ?? '');
                $deal['total_paid'] = (float)($dayPaidMap[$did] ?? 0);
            }
            unset($deal);
            $payments = [];
            foreach ($dayPaidMap as $dealId => $amount) {
                $payments[] = [
                    'deal_id' => (int)$dealId,
                    'amount' => (float)$amount,
                    'payment_date' => $rowDate
                ];
            }
            $result['payments'] = $payments;
            $result['meta']['total_payments'] = count($payments);
            return $result;
        }

        $dailyFilters = $filters;
        $dailyFilters['date_from'] = $rowDate;
        $dailyFilters['date_to'] = $rowDate;
        $dailyFilters['person_date_from'] = $rowDate;
        $dailyFilters['person_date_to'] = $rowDate;

        $pdo = $this->db->getPdo();
        $paymentsByDealDateSql = $this->buildKpiPaymentsWindowByDealDateSql($dailyFilters, 'sales');
        $whereDeals = $this->buildWhereClauseForDealsNoDate($dailyFilters, 'd');
        $paramsDeals = $this->buildParamsForDealsNoDate($dailyFilters);

        $sql = "
            SELECT
                d.id as deal_id,
                SUM(pay.amount) as day_paid
            FROM deals d
            INNER JOIN ({$paymentsByDealDateSql}) pay ON pay.deal_id = d.id
            WHERE {$whereDeals}
              AND pay.payment_date = ?
            GROUP BY d.id
        ";
        $rows = $this->runSelectAll($sql, array_merge($paramsDeals, [$rowDate]));
        $dealIds = [];
        $dayPaidMap = [];
        foreach ($rows as $row) {
            $id = (int)($row['deal_id'] ?? 0);
            if ($id <= 0) {
                continue;
            }
            $dealIds[] = $id;
            $dayPaidMap[(string)$id] = (float)($row['day_paid'] ?? 0);
        }
        if (empty($dealIds)) {
            return ['leads' => [], 'deals' => [], 'activities' => [], 'payments' => [], 'meta' => ['total_leads' => 0, 'total_deals' => 0, 'total_activities' => 0, 'total_payments' => 0]];
        }

        $result = $this->buildSimpleDealLeadDrilldownByIds($dealIds, $dailyFilters);
        foreach ($result['deals'] as &$deal) {
            $did = (string)($deal['deal_id'] ?? '');
            $deal['total_paid'] = (float)($dayPaidMap[$did] ?? 0);
        }
        unset($deal);
        $payments = [];
        foreach ($dayPaidMap as $dealId => $amount) {
            $payments[] = [
                'deal_id' => (int)$dealId,
                'amount' => (float)$amount,
                'payment_date' => $rowDate
            ];
        }
        $result['payments'] = $payments;
        $result['meta']['total_payments'] = count($payments);
        return $result;
    }

    private function getSalesUiOracle(array $filters): array
    {
        $oracle = $this->getUiOracleKpiData($filters);
        $row = $oracle['sales_card'] ?? [];
        return [
            'deal_count' => (int)($row['deal_count'] ?? 0),
            'total_contract_amount' => (float)($row['total_contract_amount'] ?? 0),
            'total_collected_amount' => (float)($row['total_collected_amount'] ?? 0),
        ];
    }

    private function getSalesTotalCardDrilldownUiOracle(array $filters, string $rowOwnerDidarId = ''): array
    {
        $oracle = $this->getUiOracleKpiData($filters);
        $idMap = [];
        foreach (($oracle['sets']['sales_card_qualified'] ?? []) as $dealId => $_) {
            $idMap[(int)$dealId] = true;
        }
        $ids = array_keys($idMap);
        if ($rowOwnerDidarId !== '') {
            $ids = array_values(array_filter($ids, function ($dealId) use ($oracle, $rowOwnerDidarId) {
                $deal = $oracle['state']['deals'][(string)$dealId] ?? null;
                return $deal && (string)($deal['owner_didar_id'] ?? '') === $rowOwnerDidarId;
            }));
        }
        $result = $this->buildSimpleDealLeadDrilldownByIds($ids, $filters, 'sales');
        $salesAmountByDeal = [];
        $salesDateByDeal = [];
        foreach ($ids as $dealId) {
            $key = (string)$dealId;
            $salesAmountByDeal[$key] = (float)($oracle['aggs']['sales_window'][$key]['total'] ?? 0);
            $salesDateByDeal[$key] = (string)($oracle['aggs']['sales_first_all'][$key]['first'] ?? '');
        }
        foreach ($result['deals'] as &$deal) {
            $dealId = (string)($deal['deal_id'] ?? '');
            if ($dealId !== '' && array_key_exists($dealId, $salesAmountByDeal)) {
                $deal['total_paid'] = $salesAmountByDeal[$dealId];
                $deal['first_payment_date'] = $salesDateByDeal[$dealId] ?? ($deal['first_payment_date'] ?? '');
            }
        }
        unset($deal);
        $payments = [];
        foreach ($salesAmountByDeal as $dealId => $amount) {
            if ($amount <= 0) {
                continue;
            }
            $payments[] = [
                'deal_id' => (int)$dealId,
                'amount' => (float)$amount,
                'payment_date' => $salesDateByDeal[$dealId] ?? '',
            ];
        }
        $result['payments'] = $payments;
        $result['meta']['total_payments'] = count($payments);
        return $result;
    }

    private function getProductSalesShareChartDrilldown(array $filters, string $rowProductName): array
    {
        if ($rowProductName === '') {
            return ['leads' => [], 'deals' => [], 'activities' => [], 'payments' => [], 'meta' => ['total_leads' => 0, 'total_deals' => 0, 'total_activities' => 0, 'total_payments' => 0]];
        }
        if ($this->getKpiPaymentDateBasis($filters, 'sales') === 'ui_payment_date') {
            $oracle = $this->getUiOracleKpiData($filters);
            $targetName = $this->normalizeProductName($rowProductName);
            $ids = [];
            foreach (($oracle['sets']['sales_card_qualified'] ?? []) as $dealId => $_) {
                $deal = $oracle['state']['deals'][(string)$dealId] ?? null;
                if (!$deal) {
                    continue;
                }
                $pid = $this->uiOraclePrimaryProductIdForDeal($deal, $oracle['state']['deal_products'] ?? []);
                $resolved = $this->uiOracleResolvedProductName(
                    $deal,
                    $pid,
                    $oracle['state']['product_names'] ?? [],
                    $oracle['state']['dealProductSnapshots'] ?? []
                );
                if ($resolved !== '' && $this->normalizeProductName($resolved) === $targetName) {
                    $ids[] = (int)$dealId;
                }
            }
            return $this->buildSimpleDealLeadDrilldownByIds($ids, $filters, 'sales');
        }
        $pdo = $this->db->getPdo();
        $dealProductHintExpr = $this->buildDealProductHintExpr('d');
        $resolvedProductExpr = $this->buildResolvedProductNameExpr($dealProductHintExpr, 'p', 'dp');
        $primaryDealProductSql = $this->buildPrimaryDealProductSql();
        $paymentsByDealDateSql = $this->buildKpiPaymentsWindowByDealDateSql($filters, 'sales');
        $whereDeals = $this->buildWhereClauseForDealsNoDate($filters, 'd');
        $paramsDeals = $this->buildParamsForDealsNoDate($filters);

        $paymentDateWhere = [];
        $paymentParams = [];
        if (!empty($filters['date_from'])) {
            $paymentDateWhere[] = "pbd.payment_date >= ?";
            $paymentParams[] = $filters['date_from'];
        }
        if (!empty($filters['date_to'])) {
            $paymentDateWhere[] = "pbd.payment_date <= ?";
            $paymentParams[] = $filters['date_to'];
        }
        $paymentDateClause = !empty($paymentDateWhere) ? implode(' AND ', $paymentDateWhere) : '1=1';

        $sql = "
            SELECT DISTINCT d.id as deal_id
            FROM deals d
            INNER JOIN (
                SELECT DISTINCT pbd.deal_id
                FROM ({$paymentsByDealDateSql}) pbd
                WHERE {$paymentDateClause}
            ) pay ON pay.deal_id = d.id
            INNER JOIN ({$primaryDealProductSql}) dp ON dp.deal_id = d.id
            LEFT JOIN products p ON p.id = dp.product_id
            WHERE {$whereDeals}
              AND {$resolvedProductExpr} = ?
        ";
        $rows = $this->runSelectAll($sql, array_merge($paymentParams, $paramsDeals, [$rowProductName]));
        $dealIds = array_map('intval', array_column($rows, 'deal_id'));
        return $this->buildSimpleDealLeadDrilldownByIds($dealIds, $filters);
    }

    private function getConversionFunnelChartDrilldown(array $filters, string $stage): array
    {
        $stage = trim($stage);
        if ($stage === 'لید جدید') {
            return $this->getLeadStatusDrilldown($filters, 'lead_new_count', '', '');
        }
        if ($stage === 'معامله') {
            if ($this->getKpiPaymentDateBasis($filters, 'sales') === 'ui_payment_date') {
                $oracle = $this->getUiOracleKpiData($filters);
                $ids = array_map('intval', array_keys($oracle['sets']['conversion_reference'] ?? []));
                return $this->buildSimpleDealLeadDrilldownByIds($ids, $filters);
            }
            return $this->getDealBasedDrilldown($filters, 'deal_status', 'deal_total', '', '', '', '');
        }
        if ($stage === 'پرداخت') {
            return $this->getSalesTotalCardDrilldown($filters, '');
        }
        if ($stage === 'تسویه کامل') {
            if ($this->getKpiPaymentDateBasis($filters, 'payment') === 'ui_payment_date') {
                return $this->getFinancialPerformanceCardDrilldownUiOracle($filters, 'settled', '');
            }
            return $this->getDealBasedDrilldown($filters, 'payment_status', 'settled_deals_count', '', '', '', '');
        }
        return ['leads' => [], 'deals' => [], 'activities' => [], 'payments' => [], 'meta' => ['total_leads' => 0, 'total_deals' => 0, 'total_activities' => 0, 'total_payments' => 0]];
    }

    private function getDealBasedDrilldown(
        array $filters,
        string $tableId,
        string $metricKey,
        string $rowDate,
        string $rowOwnerDidarId,
        string $rowProductName,
        string $rowFailureReason
    ): array {
        if (
            $tableId === 'payment_status'
            && $rowDate === ''
            && in_array($metricKey, ['settled_deals_count', 'outstanding_amount_sum'], true)
            && $this->getKpiPaymentDateBasis($filters, 'payment') === 'ui_payment_date'
        ) {
            return $this->getFinancialPerformanceCardDrilldownUiOracle(
                $filters,
                $metricKey === 'settled_deals_count' ? 'settled' : 'outstanding',
                $rowOwnerDidarId
            );
        }
        $pdo = $this->db->getPdo();
        $condition = '1=1';
        $conditionParams = [];

        switch ($tableId) {
            case 'deal_status':
                $map = [
                    'deal_total' => '1=1',
                    'deal_new_total' => "b.lead_type = 'new'",
                    'deal_old_total' => "b.lead_type = 'old'",
                    'deal_pending_total' => "b.deal_status_group = 'pending'",
                    'deal_pending_new' => "b.deal_status_group = 'pending' AND b.lead_type = 'new'",
                    'deal_pending_old' => "b.deal_status_group = 'pending' AND b.lead_type = 'old'",
                    'deal_with_sales_total' => "b.has_payment = 1",
                    'deal_with_sales_new' => "b.has_payment = 1 AND b.lead_type = 'new'",
                    'deal_with_sales_old' => "b.has_payment = 1 AND b.lead_type = 'old'",
                    'deal_without_sales_total' => "b.has_payment = 0 AND b.deal_status_group = 'pending'",
                    'deal_without_sales_new' => "b.has_payment = 0 AND b.deal_status_group = 'pending' AND b.lead_type = 'new'",
                    'deal_without_sales_old' => "b.has_payment = 0 AND b.deal_status_group = 'pending' AND b.lead_type = 'old'",
                    'deal_won_total' => "b.deal_status_group = 'won'",
                    'deal_won_new' => "b.deal_status_group = 'won' AND b.lead_type = 'new'",
                    'deal_won_old' => "b.deal_status_group = 'won' AND b.lead_type = 'old'",
                    'deal_lost_total' => "b.deal_status_group = 'lost'",
                    'deal_lost_new' => "b.deal_status_group = 'lost' AND b.lead_type = 'new'",
                    'deal_lost_old' => "b.deal_status_group = 'lost' AND b.lead_type = 'old'",
                ];
                $condition = $map[$metricKey] ?? '1=1';
                break;

            case 'payment_mix':
                $map = [
                    'new_leads_with_payment' => "b.lead_type = 'new' AND b.has_payment = 1",
                    'new_leads_without_payment' => "b.lead_type = 'new' AND b.has_payment = 0 AND b.deal_status_group <> 'lost'",
                    'new_leads_failed' => "b.lead_type = 'new' AND b.deal_status_group = 'lost'",
                    'old_leads_with_payment' => "b.lead_type = 'old' AND b.has_payment = 1",
                    'old_leads_without_payment' => "b.lead_type = 'old' AND b.has_payment = 0 AND b.deal_status_group <> 'lost'",
                    'old_leads_failed' => "b.lead_type = 'old' AND b.deal_status_group = 'lost'",
                ];
                $condition = $map[$metricKey] ?? '1=1';
                break;

            case 'payment_status':
                $map = [
                    'outstanding_deals_count' => "b.has_payment = 1 AND b.payable_amount > 0 AND b.total_paid < b.payable_amount",
                    'settled_deals_count' => "b.has_payment = 1 AND b.payable_amount > 0 AND b.total_paid >= b.payable_amount",
                    'outstanding_amount_sum' => "b.has_payment = 1 AND b.payable_amount > 0 AND b.total_paid < b.payable_amount",
                    'settled_contract_amount_sum' => "b.has_payment = 1 AND b.payable_amount > 0 AND b.total_paid >= b.payable_amount",
                    'payments_sum' => "b.has_payment = 1",
                ];
                $condition = $map[$metricKey] ?? '1=1';
                break;

            case 'conversion_status':
                $map = [
                    'conversion_rate_total' => '1=1',
                    'non_conversion_rate_total' => '1=1',
                    'conversion_rate_new' => "b.lead_type = 'new'",
                    'non_conversion_rate_new' => "b.lead_type = 'new'",
                    'conversion_rate_old' => "b.lead_type = 'old'",
                    'non_conversion_rate_old' => "b.lead_type = 'old'",
                    'sales_total_count' => "b.has_payment = 1",
                    'sales_new_count' => "b.has_payment = 1 AND b.lead_type = 'new'",
                    'sales_old_count' => "b.has_payment = 1 AND b.lead_type = 'old'",
                ];
                $condition = $map[$metricKey] ?? '1=1';
                break;

            case 'conversion_time':
                $map = [
                    'avg_conversion_days_total' => "b.has_payment = 1 AND b.first_payment_date IS NOT NULL",
                    'avg_conversion_days_new' => "b.has_payment = 1 AND b.first_payment_date IS NOT NULL AND b.lead_type = 'new'",
                    'avg_conversion_days_old' => "b.has_payment = 1 AND b.first_payment_date IS NOT NULL AND b.lead_type = 'old'",
                    'avg_failure_days_total' => "b.deal_status_group = 'lost' AND b.lost_date IS NOT NULL",
                    'avg_failure_days_new' => "b.deal_status_group = 'lost' AND b.lost_date IS NOT NULL AND b.lead_type = 'new'",
                    'avg_failure_days_old' => "b.deal_status_group = 'lost' AND b.lost_date IS NOT NULL AND b.lead_type = 'old'",
                ];
                $condition = $map[$metricKey] ?? '1=1';
                break;

            case 'failure_reasons':
                $condition = "b.deal_status_group = 'lost'";
                if ($rowFailureReason !== '') {
                    $condition .= " AND b.failure_reason = ?";
                    $conditionParams[] = $rowFailureReason;
                }
                if ($metricKey === 'failed_without_current_count') {
                    $condition .= " AND b.has_open_deal = 0";
                } elseif ($metricKey === 'failed_new_count') {
                    $condition .= " AND b.lead_type = 'new'";
                } elseif ($metricKey === 'failed_old_count') {
                    $condition .= " AND b.lead_type = 'old'";
                }
                break;

            case 'product_status':
                $condition = "b.has_payment = 1";
                if ($metricKey === 'sales_new_count') {
                    $condition .= " AND b.lead_type = 'new'";
                } elseif ($metricKey === 'sales_old_count') {
                    $condition .= " AND b.lead_type = 'old'";
                }
                break;

            case 'product_sold_by_owner_day':
                $condition = "b.has_payment = 1";
                if ($metricKey === 'sold_new_count') {
                    $condition .= " AND b.lead_type = 'new'";
                } elseif ($metricKey === 'sold_old_count') {
                    $condition .= " AND b.lead_type = 'old'";
                }
                break;

            case 'product_requests_by_owner_day':
                $condition = '1=1';
                if ($metricKey === 'requested_new_count') {
                    $condition .= " AND b.lead_type = 'new'";
                } elseif ($metricKey === 'requested_old_count') {
                    $condition .= " AND b.lead_type = 'old'";
                } elseif ($metricKey === 'sold_total_count') {
                    $condition .= " AND b.has_payment = 1";
                } elseif ($metricKey === 'unsold_total_count') {
                    $condition .= " AND b.has_payment = 0";
                }
                break;

            case 'product_failed_by_owner_day':
                $condition = "b.has_payment = 0";
                if ($metricKey === 'failed_new_count') {
                    $condition .= " AND b.lead_type = 'new'";
                } elseif ($metricKey === 'failed_old_count') {
                    $condition .= " AND b.lead_type = 'old'";
                }
                break;

            case 'summary_by_user':
                $map = [
                    'total_deals_count' => '1=1',
                    'sales_total_count' => 'b.has_payment = 1',
                    'conversion_rate_total' => '1=1',
                    'total_new_deals_count' => "b.lead_type = 'new'",
                    'sales_new_count' => "b.lead_type = 'new' AND b.has_payment = 1",
                    'conversion_rate_new' => "b.lead_type = 'new'",
                    'total_collected_amount' => 'b.has_payment = 1',
                    'outstanding_amount_sum' => "b.has_payment = 1 AND b.payable_amount > 0 AND b.total_paid < b.payable_amount",
                ];
                $condition = $map[$metricKey] ?? '1=1';
                break;
        }

        // For payment_status table, use payment date filtering instead of register time
        // This ensures consistency with the summary card calculation
        $usePaymentDateFilter = ($tableId === 'payment_status');
        $useLostDateFilter = (
            $tableId === 'deal_status'
            && $rowDate === ''
            && in_array($metricKey, ['deal_lost_total', 'deal_lost_new', 'deal_lost_old'], true)
        );

        $baseParams = [];
        if ($usePaymentDateFilter) {
            $dealsBaseSql = $this->buildDrilldownDealsByPaymentDateSql($filters, $rowOwnerDidarId, $baseParams);
        } elseif ($useLostDateFilter) {
            $dealsBaseSql = $this->buildDrilldownDealsByLostDateSql($filters, $rowOwnerDidarId, $baseParams);
        } else {
            $dealsBaseSql = $this->buildDrilldownDealsBaseSql($filters, $rowDate, $rowOwnerDidarId, $baseParams, $tableId !== 'summary_by_user');
        }

        $joinProduct = in_array($tableId, ['product_status', 'product_sold_by_owner_day', 'product_requests_by_owner_day', 'product_failed_by_owner_day'], true);
        $productWhere = '';
        $productParams = [];
        $resolvedProductExpr = $this->buildResolvedProductNameExpr('b.deal_product_hint', 'pr', 'dp');
        $primaryDealProductSql = $this->buildPrimaryDealProductSql();
        if ($joinProduct && $rowProductName !== '') {
            $productWhere = " AND {$resolvedProductExpr} = ?";
            $productParams[] = $rowProductName;
        }

        $matchedDealsSql = "
            SELECT DISTINCT b.*
            FROM ({$dealsBaseSql}) b
            " . ($joinProduct ? "INNER JOIN ({$primaryDealProductSql}) dp ON dp.deal_id = b.deal_id LEFT JOIN products pr ON pr.id = dp.product_id" : "") . "
            WHERE {$condition}
            {$productWhere}
        ";
        $matchedParams = array_merge($baseParams, $conditionParams, $productParams);

        // conversion_time needs the first payment date per deal to compute days-to-convert.
        $conversionSelect = ($tableId === 'conversion_time') ? "                b.first_payment_date,\n" : '';
        $dealsSql = "
            SELECT
                b.deal_id,
                b.didar_deal_id,
                b.title,
                b.deal_date,
{$conversionSelect}                b.owner_name,
                b.owner_didar_id,
                b.contact_didar_id,
                b.lead_sale_status,
                b.lead_register_time,
                b.lead_register_time_jalali,
                b.deal_status_group,
                b.payable_amount,
                b.total_paid,
                b.has_payment,
                b.failure_reason
            FROM ({$matchedDealsSql}) b
            ORDER BY b.deal_date DESC, b.deal_id DESC
        ";
        $deals = $this->runSelectAll($dealsSql, $matchedParams);

        $leadsSql = "
            SELECT DISTINCT
                p.didar_contact_id as contact_id,
                TRIM(CONCAT(COALESCE(p.first_name, ''), ' ', COALESCE(p.last_name, ''))) as full_name,
                p.mobile_phone,
                p.secondary_mobile_phone,
                DATE(COALESCE(p.register_time, p.created_at)) as lead_date,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name
            FROM persons p
            LEFT JOIN users u ON u.didar_user_id = p.owner_didar_id
            INNER JOIN (
                SELECT DISTINCT contact_didar_id
                FROM ({$matchedDealsSql}) m
                WHERE COALESCE(m.contact_didar_id, '') <> ''
            ) md ON md.contact_didar_id = p.didar_contact_id
            ORDER BY lead_date DESC, full_name ASC
        ";
        $leads = $this->runSelectAll($leadsSql, $matchedParams);

        $dealIds = array_values(array_filter(array_map(static function ($row) {
            return (int)($row['deal_id'] ?? 0);
        }, $deals)));
        $payments = $this->getPaymentsByDealIds($dealIds);

        $meta = [
            'total_leads' => count($leads),
            'total_deals' => count($deals),
            'total_activities' => 0,
            'total_payments' => count($payments)
        ];
        if ($tableId === 'conversion_time') {
            $meta['drilldown_kind'] = 'conversion_time';
            $meta['row_type'] = 'deal';
            $meta['title'] = 'میانگین زمان تبدیل';
        }

        return [
            'leads' => $leads,
            'deals' => $deals,
            'activities' => [],
            'payments' => $payments,
            'meta' => $meta
        ];
    }

    private function getLeadStatusDrilldown(array $filters, string $metricKey, string $rowDate, string $rowOwnerDidarId): array
    {
        $pdo = $this->db->getPdo();
        $paymentsByDealSql = $this->buildUnifiedPaymentsByDealSql();
        $params = [];
        [$dateFrom, $dateTo] = $this->dateRangeFromFilters($filters);
        $whereParts = ["DATE(COALESCE(p.register_time, p.created_at)) BETWEEN ? AND ?"];
        $params[] = $dateFrom;
        $params[] = $dateTo;

        if ($rowDate !== '') {
            $whereParts[] = "DATE(COALESCE(p.register_time, p.created_at)) = ?";
            $params[] = $rowDate;
        }
        if ($rowOwnerDidarId !== '') {
            $whereParts[] = "p.owner_didar_id = ?";
            $params[] = $rowOwnerDidarId;
        } elseif (!empty($filters['owner_didar_ids_csv'])) {
            $whereParts[] = "FIND_IN_SET(p.owner_didar_id, ?)";
            $params[] = $filters['owner_didar_ids_csv'];
        } elseif (!empty($filters['owner_didar_id'])) {
            $whereParts[] = "p.owner_didar_id = ?";
            $params[] = $filters['owner_didar_id'];
        }

        $condition = '1=1';
        if ($metricKey === 'leads_with_sales') {
            $condition = "x.has_sale_payment = 1";
        } elseif ($metricKey === 'leads_with_new_sales') {
            $condition = "x.has_sale_payment = 1 AND x.first_payment_date = x.lead_date";
        } elseif ($metricKey === 'leads_with_previous_sales') {
            $condition = "x.has_sale_payment = 1 AND x.first_payment_date < x.lead_date";
        } elseif ($metricKey === 'leads_without_sales') {
            $condition = "x.has_sale_payment = 0";
        } elseif ($metricKey === 'leads_without_sales_with_current_deal') {
            $condition = "x.has_sale_payment = 0 AND x.has_open_deal = 1";
        } elseif ($metricKey === 'leads_without_sales_without_current_deal') {
            $condition = "x.has_sale_payment = 0 AND x.has_open_deal = 0";
        }

        $legacyOpenClause = $this->buildLegacyDealFilterClause($filters, 'd2');
        $legacyOpenClause = $legacyOpenClause !== '' ? (' AND ' . $legacyOpenClause) : '';
        $legacyDealsClauseForD4 = $this->buildLegacyDealFilterClause($filters, 'd4');
        $legacyDealsClauseForD4 = $legacyDealsClauseForD4 !== '' ? (' AND ' . $legacyDealsClauseForD4) : '';

        $leadsSql = "
            SELECT
                x.contact_id,
                x.full_name,
                x.mobile_phone,
                x.secondary_mobile_phone,
                x.lead_date,
                x.owner_name
            FROM (
                SELECT
                    p.didar_contact_id as contact_id,
                    TRIM(CONCAT(COALESCE(p.first_name, ''), ' ', COALESCE(p.last_name, ''))) as full_name,
                    p.mobile_phone,
                    p.secondary_mobile_phone,
                    DATE(COALESCE(p.register_time, p.created_at)) as lead_date,
                    COALESCE(u.display_name, 'بدون مسئول') as owner_name,
                    CASE WHEN COALESCE(od.open_count, 0) > 0 THEN 1 ELSE 0 END as has_open_deal,
                    cp.first_payment_date,
                    CASE WHEN cp.first_payment_date IS NOT NULL THEN 1 ELSE 0 END as has_sale_payment
                FROM persons p
                LEFT JOIN users u ON u.didar_user_id = p.owner_didar_id
                LEFT JOIN (
                    SELECT
                        d2.contact_didar_id,
                        COUNT(*) as open_count
                    FROM deals d2
                    WHERE d2.status NOT IN ('Won', 'Lost')
                      {$legacyOpenClause}
                    GROUP BY d2.contact_didar_id
                ) od ON od.contact_didar_id = p.didar_contact_id
                LEFT JOIN (
                    SELECT
                        d4.contact_didar_id,
                        MIN(DATE(pay4.first_payment_date)) as first_payment_date
                    FROM deals d4
                    LEFT JOIN ({$paymentsByDealSql}) pay4 ON pay4.deal_id = d4.id
                    WHERE pay4.first_payment_date IS NOT NULL
                      {$legacyDealsClauseForD4}
                    GROUP BY d4.contact_didar_id
                ) cp ON cp.contact_didar_id = p.didar_contact_id
                WHERE " . implode(' AND ', $whereParts) . "
            ) x
            WHERE {$condition}
            ORDER BY x.lead_date DESC, x.full_name ASC
        ";
        $leads = $this->runSelectAll($leadsSql, $params);

        $leadIds = array_values(array_filter(array_map(static function ($row) {
            return $row['contact_id'] ?? '';
        }, $leads)));

        $deals = [];
        $payments = [];
        if (!empty($leadIds)) {
            $placeholders = implode(',', array_fill(0, count($leadIds), '?'));
            $dealsWhere = $this->buildWhereClauseForDeals($filters, 'd');
            $dealParams = $this->buildParamsForDeals($filters);
            $dealParams = array_merge($dealParams, $leadIds);
            $paymentsByDealSql2 = $this->buildUnifiedPaymentsByDealSql();
            $dealsSql = "
                SELECT
                    d.id as deal_id,
                    d.didar_deal_id,
                    d.title,
                    DATE(COALESCE(d.register_time, d.created_at)) as deal_date,
                    COALESCE(u.display_name, 'بدون مسئول') as owner_name,
                    d.owner_didar_id,
                    d.contact_didar_id,
                    COALESCE(p.sale_status, '') as lead_sale_status,
                    DATE(COALESCE(p.register_time, p.created_at)) as lead_register_time,
                    COALESCE(p.register_time_jalali, '') as lead_register_time_jalali,
                    CASE WHEN d.status = 'Won' THEN 'won' WHEN d.status = 'Lost' THEN 'lost' ELSE 'pending' END as deal_status_group,
                    COALESCE(d.payable_amount, 0) as payable_amount,
                    COALESCE(pay.total_paid, 0) as total_paid,
                    CASE WHEN COALESCE(pay.total_paid, 0) > 0 THEN 1 ELSE 0 END as has_payment
                FROM deals d
                LEFT JOIN users u ON u.didar_user_id = d.owner_didar_id
                LEFT JOIN persons p ON p.didar_contact_id = d.contact_didar_id
                LEFT JOIN ({$paymentsByDealSql2}) pay ON pay.deal_id = d.id
                WHERE {$dealsWhere}
                  AND d.contact_didar_id IN ({$placeholders})
                ORDER BY DATE(COALESCE(d.register_time, d.created_at)) DESC, d.id DESC
            ";
            $deals = $this->runSelectAll($dealsSql, $dealParams);

            $dealIds = array_values(array_filter(array_map(static function ($row) {
                return (int)($row['deal_id'] ?? 0);
            }, $deals)));
            $payments = $this->getPaymentsByDealIds($dealIds);
        }

        return [
            'leads' => $leads,
            'deals' => $deals,
            'activities' => [],
            'payments' => $payments,
            'meta' => [
                'total_leads' => count($leads),
                'total_deals' => count($deals),
                'total_activities' => 0,
                'total_payments' => count($payments)
            ]
        ];
    }

    private function getActivityStatusDrilldown(
        array $filters,
        string $metricKey,
        string $rowDate,
        string $rowOwnerDidarId,
        string $rowActivityType
    ): array {
        [$dateFrom, $dateTo] = $this->dateRangeFromFilters($filters);

        $activityWhere = ["DATE(COALESCE(a.register_date, a.created_at)) BETWEEN ? AND ?"];
        $activityParams = [$dateFrom, $dateTo];

        if ($rowDate !== '') {
            $activityWhere[] = "DATE(COALESCE(a.register_date, a.created_at)) = ?";
            $activityParams[] = $rowDate;
        }
        if ($rowOwnerDidarId !== '') {
            $activityWhere[] = "a.owner_didar_id = ?";
            $activityParams[] = $rowOwnerDidarId;
        } elseif (!empty($filters['owner_didar_ids_csv'])) {
            $activityWhere[] = "FIND_IN_SET(a.owner_didar_id, ?)";
            $activityParams[] = $filters['owner_didar_ids_csv'];
        } elseif (!empty($filters['owner_didar_id'])) {
            $activityWhere[] = "a.owner_didar_id = ?";
            $activityParams[] = $filters['owner_didar_id'];
        }

        $activityTypeExpr = $this->buildCanonicalActivityTypeExpr('a');
        $metricCondition = '1=1';
        if ($metricKey === 'activities_new_lead') {
            $metricCondition = "DATE(COALESCE(p.register_time, p.created_at)) = DATE(COALESCE(a.register_date, a.created_at))";
        } elseif ($metricKey === 'activities_old_lead') {
            $metricCondition = "DATE(COALESCE(p.register_time, p.created_at)) < DATE(COALESCE(a.register_date, a.created_at))";
        } elseif ($metricKey === 'no_response_new') {
            $metricCondition = "(
                {$activityTypeExpr} = 'تماس عدم پاسخ'
                AND DATE(COALESCE(p.register_time, p.created_at)) = DATE(COALESCE(a.register_date, a.created_at))
            )";
        } elseif ($metricKey === 'no_response_old') {
            $metricCondition = "(
                {$activityTypeExpr} = 'تماس عدم پاسخ'
                AND DATE(COALESCE(p.register_time, p.created_at)) < DATE(COALESCE(a.register_date, a.created_at))
            )";
        } elseif ($metricKey === 'no_response_total') {
            $metricCondition = "(
                {$activityTypeExpr} = 'تماس عدم پاسخ'
            )";
        } elseif ($metricKey === 'activity_type_new_count') {
            $metricCondition = "DATE(COALESCE(p.register_time, p.created_at)) = DATE(COALESCE(a.register_date, a.created_at))";
            if ($rowActivityType !== '') {
                $metricCondition .= " AND {$activityTypeExpr} = ?";
                $activityParams[] = $rowActivityType;
            }
        } elseif ($metricKey === 'activity_type_old_count') {
            $metricCondition = "DATE(COALESCE(p.register_time, p.created_at)) < DATE(COALESCE(a.register_date, a.created_at))";
            if ($rowActivityType !== '') {
                $metricCondition .= " AND {$activityTypeExpr} = ?";
                $activityParams[] = $rowActivityType;
            }
        } elseif ($metricKey === 'activity_type' && $rowActivityType !== '') {
            $metricCondition = "{$activityTypeExpr} = ?";
            $activityParams[] = $rowActivityType;
        } elseif (in_array($metricKey, ['activity_count_total', 'activity_type_duration_minutes', 'avg_activity_type_duration_minutes', 'activity_share_percent'], true) && $rowActivityType !== '') {
            $metricCondition = "{$activityTypeExpr} = ?";
            $activityParams[] = $rowActivityType;
        }

        $activitiesSql = "
            SELECT
                a.id as activity_id,
                DATE(COALESCE(a.register_date, a.created_at)) as activity_date,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name,
                {$activityTypeExpr} as activity_type,
                COALESCE(a.duration, 0) as activity_duration_minutes,
                a.contact_didar_id,
                TRIM(CONCAT(COALESCE(p.first_name, ''), ' ', COALESCE(p.last_name, ''))) as lead_name,
                p.mobile_phone
            FROM activities a
            LEFT JOIN persons p ON p.didar_contact_id = a.contact_didar_id
            LEFT JOIN users u ON u.didar_user_id = a.owner_didar_id
            WHERE " . implode(' AND ', $activityWhere) . "
              AND {$metricCondition}
            ORDER BY activity_date DESC, a.id DESC
        ";
        $activities = $this->runSelectAll($activitiesSql, $activityParams);

        $contactIds = array_values(array_filter(array_unique(array_map(static function ($row) {
            return $row['contact_didar_id'] ?? '';
        }, $activities))));

        $leads = [];
        $deals = [];
        $payments = [];
        if (!empty($contactIds)) {
            $placeholders = implode(',', array_fill(0, count($contactIds), '?'));

            $leadSql = "
                SELECT
                    p.didar_contact_id as contact_id,
                    TRIM(CONCAT(COALESCE(p.first_name, ''), ' ', COALESCE(p.last_name, ''))) as full_name,
                    p.mobile_phone,
                    p.secondary_mobile_phone,
                    DATE(COALESCE(p.register_time, p.created_at)) as lead_date,
                    COALESCE(u.display_name, 'بدون مسئول') as owner_name
                FROM persons p
                LEFT JOIN users u ON u.didar_user_id = p.owner_didar_id
                WHERE p.didar_contact_id IN ({$placeholders})
                ORDER BY lead_date DESC, full_name ASC
            ";
            $leads = $this->runSelectAll($leadSql, $contactIds);

            $paymentsByDealSql = $this->buildUnifiedPaymentsByDealSql();
            $dealsWhere = $this->buildWhereClauseForDeals($filters, 'd');
            $dealParams = $this->buildParamsForDeals($filters);
            $dealParams = array_merge($dealParams, $contactIds);
            $dealsSql = "
                SELECT
                    d.id as deal_id,
                    d.didar_deal_id,
                    d.title,
                    DATE(COALESCE(d.register_time, d.created_at)) as deal_date,
                    COALESCE(u.display_name, 'بدون مسئول') as owner_name,
                    d.owner_didar_id,
                    d.contact_didar_id,
                    CASE WHEN d.status = 'Won' THEN 'won' WHEN d.status = 'Lost' THEN 'lost' ELSE 'pending' END as deal_status_group,
                    COALESCE(d.payable_amount, 0) as payable_amount,
                    COALESCE(pay.total_paid, 0) as total_paid,
                    CASE WHEN COALESCE(pay.total_paid, 0) > 0 THEN 1 ELSE 0 END as has_payment
                FROM deals d
                LEFT JOIN users u ON u.didar_user_id = d.owner_didar_id
                LEFT JOIN ({$paymentsByDealSql}) pay ON pay.deal_id = d.id
                WHERE {$dealsWhere}
                  AND d.contact_didar_id IN ({$placeholders})
                ORDER BY deal_date DESC, d.id DESC
            ";
            $deals = $this->runSelectAll($dealsSql, $dealParams);

            $dealIds = array_values(array_filter(array_map(static function ($row) {
                return (int)($row['deal_id'] ?? 0);
            }, $deals)));
            $payments = $this->getPaymentsByDealIds($dealIds);
        }

        return [
            'leads' => $leads,
            'deals' => $deals,
            'activities' => $activities,
            'payments' => $payments,
            'meta' => [
                'total_leads' => count($leads),
                'total_deals' => count($deals),
                'total_activities' => count($activities),
                'total_payments' => count($payments)
            ]
        ];
    }

    private function getPaymentsByDealIds(array $dealIds): array
    {
        if (empty($dealIds)) {
            return [];
        }

        $dealIds = array_values(array_unique(array_map('intval', $dealIds)));
        $placeholders = implode(',', array_fill(0, count($dealIds), '?'));
        $txColumns = [];
        $pickTxExpr = function (string $column, string $fallback = "''") use (&$txColumns): string {
            if (!array_key_exists($column, $txColumns)) {
                $txColumns[$column] = $this->db->hasColumn('transactions', $column);
            }
            return $txColumns[$column] ? "t.{$column}" : $fallback;
        };
        $paymentDateExpr = $pickTxExpr('payment_date');
        $amountExpr = $pickTxExpr('amount', '0');
        $hasDealInvoiceCode = $this->db->hasColumn('deals', 'invoice_code');
        $dealInvoiceExpr = $hasDealInvoiceCode ? "COALESCE(d.invoice_code, '')" : "''";
        $payments = [];

        // UI payment date is stored in deals.payments. Prefer it over transactions
        // for drill-down exports so dates match what agents entered in the UI.
        $dealsSql = "
            SELECT
                d.id as deal_id,
                d.didar_deal_id,
                d.title as deal_title,
                COALESCE(d.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name,
                {$dealInvoiceExpr} as invoice_code,
                COALESCE(p.customer_code, '') as customer_code,
                COALESCE(d.contact_didar_id, '') as contact_didar_id,
                TRIM(CONCAT(COALESCE(p.first_name, ''), ' ', COALESCE(p.last_name, ''))) as contact_name,
                d.payments as payments_json
            FROM deals d
            LEFT JOIN users u ON u.didar_user_id = d.owner_didar_id
            LEFT JOIN persons p ON p.didar_contact_id = d.contact_didar_id
            WHERE d.id IN ({$placeholders})
              AND COALESCE(d.payments, '') <> ''
              AND JSON_VALID(d.payments)
              AND COALESCE(JSON_LENGTH(d.payments), 0) > 0
        ";
        $dealRows = $this->runSelectAll($dealsSql, $dealIds);
        foreach ($dealRows as $dealRow) {
            $decoded = json_decode((string)($dealRow['payments_json'] ?? ''), true);
            if (!is_array($decoded)) {
                continue;
            }
            foreach ($decoded as $item) {
                if (!is_array($item)) {
                    continue;
                }
                $amount = (float)($item['amount'] ?? 0);
                if ($amount <= 0) {
                    continue;
                }
                $paymentDate = $this->uiOracleNormalizeDate((string)($item['date'] ?? ''));
                if ($paymentDate === '') {
                    $paymentDate = $this->uiOracleNormalizeDate((string)($item['payment_date'] ?? ''));
                }
                if ($paymentDate === '') {
                    $paymentDate = $this->uiOracleJalaliToGregorianString((string)($item['date_persian'] ?? ''));
                }
                if ($paymentDate === '') {
                    continue;
                }
                $payments[] = [
                    'transaction_id' => '',
                    'deal_id' => (int)($dealRow['deal_id'] ?? 0),
                    'payment_date' => $paymentDate,
                    'amount' => $amount,
                    'payment_status' => (string)($item['status'] ?? ''),
                    'payment_method' => (string)(($item['method'] ?? '') ?: ($item['payment_method'] ?? '')),
                    'payment_type' => (string)(($item['method'] ?? '') ?: ($item['payment_method'] ?? '')),
                    'card_last4' => (string)(($item['card_last4'] ?? '') ?: ($item['card_last_4'] ?? '')),
                    'destination_bank' => (string)(($item['destination_bank'] ?? '') ?: ($item['bank_destination'] ?? '')),
                    'tracking_code_last4' => (string)(($item['tracking_code_last4'] ?? '') ?: ($item['tracking_code'] ?? '')),
                    'destination_card_number' => (string)($item['destination_card_number'] ?? ''),
                    'payment_time' => (string)($item['payment_time'] ?? ''),
                    'account_owner_name' => (string)($item['account_owner_name'] ?? ''),
                    'card_reader_device' => (string)($item['card_reader_device'] ?? ''),
                    'currency' => (string)($item['payment_currency'] ?? $item['currency'] ?? ''),
                    'currency_other' => (string)($item['payment_currency_other'] ?? $item['currency_other'] ?? ''),
                    'currency_amount' => (float)($item['payment_currency_amount'] ?? $item['currency_amount'] ?? 0),
                    'check_bank_name' => (string)($item['check_bank_name'] ?? ''),
                    'check_date' => (string)($item['check_date'] ?? ''),
                    'check_number' => (string)($item['check_number'] ?? ''),
                    'check_owner_name' => (string)($item['check_owner_name'] ?? ''),
                    'description' => (string)($item['description'] ?? ''),
                    'created_at' => '',
                    'invoice_code' => (string)($dealRow['invoice_code'] ?? ''),
                    'didar_deal_id' => (string)($dealRow['didar_deal_id'] ?? ''),
                    'deal_title' => (string)($dealRow['deal_title'] ?? ''),
                    'customer_code' => (string)($dealRow['customer_code'] ?? ''),
                    'contact_didar_id' => (string)($dealRow['contact_didar_id'] ?? ''),
                    'contact_name' => (string)($dealRow['contact_name'] ?? ''),
                    'owner_didar_id' => (string)($dealRow['owner_didar_id'] ?? ''),
                    'owner_name' => (string)($dealRow['owner_name'] ?? 'بدون مسئول'),
                    'payment_source' => 'deals_json'
                ];
            }
        }

        // Transactions are used only for deals without UI JSON payment history.
        $txSql = "
            SELECT
                t.id as transaction_id,
                t.deal_id,
                DATE(CASE WHEN LEFT(COALESCE({$paymentDateExpr}, ''), 10) = '0000-00-00' THEN NULL ELSE {$paymentDateExpr} END) as payment_date,
                COALESCE({$amountExpr}, 0) as amount,
                COALESCE({$pickTxExpr('status')}, '') as payment_status,
                COALESCE({$pickTxExpr('method')}, '') as payment_method,
                COALESCE({$pickTxExpr('method')}, '') as payment_type,
                COALESCE({$pickTxExpr('card_last4')}, '') as card_last4,
                COALESCE({$pickTxExpr('destination_bank')}, '') as destination_bank,
                COALESCE({$pickTxExpr('tracking_code_last4')}, '') as tracking_code_last4,
                COALESCE({$pickTxExpr('destination_card_number')}, '') as destination_card_number,
                COALESCE({$pickTxExpr('payment_time')}, '') as payment_time,
                COALESCE({$pickTxExpr('account_owner_name')}, '') as account_owner_name,
                COALESCE({$pickTxExpr('card_reader_device')}, '') as card_reader_device,
                COALESCE({$pickTxExpr('currency')}, '') as currency,
                COALESCE({$pickTxExpr('currency_other')}, '') as currency_other,
                COALESCE({$pickTxExpr('currency_amount', '0')}, 0) as currency_amount,
                COALESCE({$pickTxExpr('check_bank_name')}, '') as check_bank_name,
                COALESCE({$pickTxExpr('check_date')}, '') as check_date,
                COALESCE({$pickTxExpr('check_number')}, '') as check_number,
                COALESCE({$pickTxExpr('check_owner_name')}, '') as check_owner_name,
                COALESCE({$pickTxExpr('description')}, '') as description,
                COALESCE({$pickTxExpr('created_at')}, '') as created_at,
                COALESCE({$pickTxExpr('invoice_code', $dealInvoiceExpr)}, {$dealInvoiceExpr}) as invoice_code,
                d.didar_deal_id,
                d.title as deal_title,
                COALESCE(p.customer_code, '') as customer_code,
                COALESCE(d.contact_didar_id, '') as contact_didar_id,
                TRIM(CONCAT(COALESCE(p.first_name, ''), ' ', COALESCE(p.last_name, ''))) as contact_name,
                COALESCE(d.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name
            FROM transactions t
            LEFT JOIN deals d ON d.id = t.deal_id
            LEFT JOIN persons p ON p.didar_contact_id = d.contact_didar_id
            LEFT JOIN users u ON u.didar_user_id = d.owner_didar_id
            WHERE t.deal_id IN ({$placeholders})
              AND COALESCE({$amountExpr}, 0) > 0
              AND NOT EXISTS (
                SELECT 1
                FROM deals d2
                WHERE d2.id = t.deal_id
                  AND COALESCE(d2.payments, '') <> ''
                  AND JSON_VALID(d2.payments)
                  AND COALESCE(JSON_LENGTH(d2.payments), 0) > 0
              )
            ORDER BY payment_date DESC, t.id DESC
        ";
        $txPayments = $this->runSelectAll($txSql, $dealIds);

        foreach ($txPayments as &$row) {
            $row['payment_source'] = 'transactions';
        }
        unset($row);
        $payments = array_merge($payments, $txPayments);

        usort($payments, static function ($a, $b) {
            $dateCmp = strcmp((string)($b['payment_date'] ?? ''), (string)($a['payment_date'] ?? ''));
            if ($dateCmp !== 0) {
                return $dateCmp;
            }
            return ((int)($b['deal_id'] ?? 0)) <=> ((int)($a['deal_id'] ?? 0));
        });

        return $payments;
    }

    private function buildDrilldownDealsBaseSql(
        array $filters,
        string $rowDate,
        string $rowOwnerDidarId,
        array &$paramsOut,
        bool $applyRowDate = true
    ): string {
        $whereDeals = $this->buildWhereClauseForDeals($filters, 'd');
        $paramsDeals = $this->buildParamsForDeals($filters);

        if ($applyRowDate && $rowDate !== '') {
            $whereDeals .= " AND DATE(COALESCE(d.register_time, d.created_at)) = ?";
            $paramsDeals[] = $rowDate;
        }
        if ($rowOwnerDidarId !== '') {
            $whereDeals .= " AND d.owner_didar_id = ?";
            $paramsDeals[] = $rowOwnerDidarId;
        }

        $paymentsByDealSql = $this->buildUnifiedPaymentsByDealSql();
        $legacyOpenClause = $this->buildLegacyDealFilterClause($filters, 'd2');
        $legacyOpenClause = $legacyOpenClause !== '' ? (' AND ' . $legacyOpenClause) : '';

        $paramsOut = $paramsDeals;

        $failureReasonExpr = $this->buildFailureReasonExpr('d');

        return "
            SELECT
                d.id as deal_id,
                d.didar_deal_id,
                d.title,
                d.status,
                d.pipeline_stage,
                COALESCE(d.acquaintance_source, '') as acquaintance_source,
                COALESCE(d.acquaintance_detail, '') as acquaintance_detail,
                COALESCE(d.content_topic, '') as content_topic,
                COALESCE(d.created_by_name, '') as created_by_name,
                COALESCE(d.created_via, '') as created_via,
                DATE(COALESCE(d.register_time, d.created_at)) as deal_date,
                COALESCE(d.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name,
                COALESCE(d.contact_didar_id, '') as contact_didar_id,
                COALESCE(p.sale_status, '') as lead_sale_status,
                DATE(COALESCE(p.register_time, p.created_at)) as lead_register_time,
                COALESCE(p.register_time_jalali, '') as lead_register_time_jalali,
                {$this->buildDealProductHintExpr('d')} as deal_product_hint,
                CASE
                    WHEN DATE(COALESCE(p.register_time, p.created_at)) = DATE(COALESCE(d.register_time, d.created_at)) THEN 'new'
                    ELSE 'old'
                END as lead_type,
                CASE
                    WHEN d.status = 'Won' THEN 'won'
                    WHEN d.status = 'Lost' THEN 'lost'
                    ELSE 'pending'
                END as deal_status_group,
                COALESCE(d.payable_amount, 0) as payable_amount,
                COALESCE(pay.total_paid, 0) as total_paid,
                pay.first_payment_date,
                DATE(COALESCE(d.lost_time, d.created_at)) as lost_date,
                CASE WHEN COALESCE(pay.total_paid, 0) > 0 THEN 1 ELSE 0 END as has_payment,
                CASE WHEN COALESCE(od.open_count, 0) > 0 THEN 1 ELSE 0 END as has_open_deal,
                {$failureReasonExpr} as failure_reason
            FROM deals d
            LEFT JOIN users u ON u.didar_user_id = d.owner_didar_id
            LEFT JOIN persons p ON p.didar_contact_id = d.contact_didar_id
            LEFT JOIN ({$paymentsByDealSql}) pay ON pay.deal_id = d.id
            LEFT JOIN (
                SELECT
                    d2.contact_didar_id,
                    COUNT(*) as open_count
                FROM deals d2
                WHERE d2.status NOT IN ('Won', 'Lost')
                  {$legacyOpenClause}
                GROUP BY d2.contact_didar_id
            ) od ON od.contact_didar_id = d.contact_didar_id
            WHERE {$whereDeals}
        ";
    }

    private function getWithoutPaymentCardDrilldown(array $filters, string $rowOwnerDidarId): array
    {
        if ($this->getKpiPaymentDateBasis($filters, 'sales') === 'ui_payment_date') {
            if ($rowOwnerDidarId !== '') {
                $filters['owner_didar_id'] = $rowOwnerDidarId;
                $filters['owner_didar_ids_csv'] = $rowOwnerDidarId;
                $filters['owner_didar_ids'] = [$rowOwnerDidarId];
            }
            $oracle = $this->getUiOracleKpiData($filters);
            $ids = array_map('intval', array_keys($oracle['sets']['partition_without_payment_valid'] ?? []));
            return $this->buildSimpleDealLeadDrilldownByIds($ids, $filters);
        }
        $pdo = $this->db->getPdo();
        $whereDeals = $this->buildWhereClauseForDeals($filters, 'd');
        $paramsDeals = $this->buildParamsForDeals($filters);
        if ($rowOwnerDidarId !== '') {
            $whereDeals .= " AND d.owner_didar_id = ?";
            $paramsDeals[] = $rowOwnerDidarId;
        }

        $paymentsByDealSql = $this->buildAbsolutePaymentPresenceByDealSql();
        $legacyOpenClause = $this->buildLegacyDealFilterClause($filters, 'd2');
        $legacyOpenClause = $legacyOpenClause !== '' ? (' AND ' . $legacyOpenClause) : '';
        $failureReasonExpr = $this->buildFailureReasonExpr('d');

        $dealsBaseSql = "
            SELECT
                d.id as deal_id,
                d.didar_deal_id,
                d.title,
                d.status,
                d.pipeline_stage,
                COALESCE(d.acquaintance_source, '') as acquaintance_source,
                COALESCE(d.acquaintance_detail, '') as acquaintance_detail,
                COALESCE(d.content_topic, '') as content_topic,
                COALESCE(d.created_by_name, '') as created_by_name,
                COALESCE(d.created_via, '') as created_via,
                DATE(COALESCE(d.register_time, d.created_at)) as deal_date,
                COALESCE(d.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name,
                COALESCE(d.contact_didar_id, '') as contact_didar_id,
                COALESCE(p.sale_status, '') as lead_sale_status,
                DATE(COALESCE(p.register_time, p.created_at)) as lead_register_time,
                COALESCE(p.register_time_jalali, '') as lead_register_time_jalali,
                {$this->buildDealProductHintExpr('d')} as deal_product_hint,
                CASE
                    WHEN DATE(COALESCE(p.register_time, p.created_at)) = DATE(COALESCE(d.register_time, d.created_at)) THEN 'new'
                    ELSE 'old'
                END as lead_type,
                CASE
                    WHEN d.status = 'Won' THEN 'won'
                    WHEN d.status = 'Lost' THEN 'lost'
                    ELSE 'pending'
                END as deal_status_group,
                COALESCE(d.payable_amount, 0) as payable_amount,
                COALESCE(pay.total_paid, 0) as total_paid,
                pay.first_payment_date,
                DATE(COALESCE(d.lost_time, d.created_at)) as lost_date,
                CASE WHEN COALESCE(pay.total_paid, 0) > 0 THEN 1 ELSE 0 END as has_payment,
                CASE WHEN COALESCE(od.open_count, 0) > 0 THEN 1 ELSE 0 END as has_open_deal,
                {$failureReasonExpr} as failure_reason
            FROM deals d
            LEFT JOIN users u ON u.didar_user_id = d.owner_didar_id
            LEFT JOIN persons p ON p.didar_contact_id = d.contact_didar_id
            LEFT JOIN ({$paymentsByDealSql}) pay ON pay.deal_id = d.id
            LEFT JOIN (
                SELECT
                    d2.contact_didar_id,
                    COUNT(*) as open_count
                FROM deals d2
                WHERE d2.status NOT IN ('Won', 'Lost')
                  {$legacyOpenClause}
                GROUP BY d2.contact_didar_id
            ) od ON od.contact_didar_id = d.contact_didar_id
            WHERE {$whereDeals}
        ";

        $matchedDealsSql = "
            SELECT DISTINCT b.*
            FROM ({$dealsBaseSql}) b
            WHERE b.deal_status_group = 'pending'
              AND (b.total_paid IS NULL OR b.total_paid = 0)
        ";

        $dealsSql = "
            SELECT
                b.deal_id,
                b.didar_deal_id,
                b.title,
                b.deal_date,
                b.owner_name,
                b.owner_didar_id,
                b.contact_didar_id,
                b.lead_sale_status,
                b.lead_register_time,
                b.lead_register_time_jalali,
                b.deal_status_group,
                b.payable_amount,
                b.total_paid,
                b.has_payment
            FROM ({$matchedDealsSql}) b
            ORDER BY b.deal_date DESC, b.deal_id DESC
        ";
        $deals = $this->runSelectAll($dealsSql, $paramsDeals);

        $leadsSql = "
            SELECT DISTINCT
                p.didar_contact_id as contact_id,
                TRIM(CONCAT(COALESCE(p.first_name, ''), ' ', COALESCE(p.last_name, ''))) as full_name,
                p.mobile_phone,
                p.secondary_mobile_phone,
                DATE(COALESCE(p.register_time, p.created_at)) as lead_date,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name
            FROM persons p
            LEFT JOIN users u ON u.didar_user_id = p.owner_didar_id
            INNER JOIN (
                SELECT DISTINCT contact_didar_id
                FROM ({$matchedDealsSql}) m
                WHERE COALESCE(m.contact_didar_id, '') <> ''
            ) md ON md.contact_didar_id = p.didar_contact_id
            ORDER BY lead_date DESC, full_name ASC
        ";
        $leads = $this->runSelectAll($leadsSql, $paramsDeals);

        return [
            'leads' => $leads,
            'deals' => $deals,
            'activities' => [],
            'payments' => [],
            'meta' => [
                'drilldown_kind' => 'deals_without_payment',
                'row_type' => 'deal',
                'title' => 'معاملات بدون پرداخت',
                'total_leads' => count($leads),
                'total_deals' => count($deals),
                'total_activities' => 0,
                'total_payments' => 0
            ]
        ];
    }

    /**
     * Build drilldown SQL filtered by PAYMENT DATE (not register time)
     * Used for payment_status table to ensure consistency with summary card
     */
    private function buildDrilldownDealsByPaymentDateSql(
        array $filters,
        string $rowOwnerDidarId,
        array &$paramsOut
    ): string {
        $paymentsByDealSql = $this->buildKpiAllTimePaymentTotalsByDealSql($filters, 'payment');
        $paymentsByDealDateSql = $this->buildKpiPaymentsWindowByDealDateSql($filters, 'payment');
        $legacyOpenClause = $this->buildLegacyDealFilterClause($filters, 'd2');
        $legacyOpenClause = $legacyOpenClause !== '' ? (' AND ' . $legacyOpenClause) : '';

        $parts = [];
        $params = [];

        $legacyClause = $this->buildLegacyDealFilterClause($filters, 'd');
        if ($legacyClause !== '') {
            $parts[] = $legacyClause;
        }

        // Date filter is applied in the INNER JOIN subquery, not here

        if (!empty($filters['owner_didar_ids_csv'])) {
            $parts[] = "FIND_IN_SET(d.owner_didar_id, ?)";
            $params[] = $filters['owner_didar_ids_csv'];
        } elseif (!empty($filters['owner_didar_id']) && trim((string)$filters['owner_didar_id']) !== '') {
            $parts[] = "d.owner_didar_id = ?";
            $params[] = trim((string)$filters['owner_didar_id']);
        }

        if (!empty($filters['product_ids_csv'])) {
            $parts[] = "EXISTS (SELECT 1 FROM deal_products dp WHERE dp.deal_id = d.id AND FIND_IN_SET(dp.product_id, ?))";
            $params[] = $filters['product_ids_csv'];
        } elseif (!empty($filters['product_id'])) {
            $parts[] = "EXISTS (SELECT 1 FROM deal_products dp WHERE dp.deal_id = d.id AND dp.product_id = ?)";
            $params[] = $filters['product_id'];
        }

        $statusCsv = $this->normalizeCsvList($filters['deal_statuses_csv'] ?? '');
        if ($statusCsv !== '' && !$this->csvHasAll($statusCsv, ['pending', 'won', 'lost'])) {
            $parts[] = "(
                (FIND_IN_SET('pending', ?) > 0 AND d.status NOT IN ('Won', 'Lost'))
                OR (FIND_IN_SET('won', ?) > 0 AND d.status = 'Won')
                OR (FIND_IN_SET('lost', ?) > 0 AND d.status = 'Lost')
            )";
            $params[] = $statusCsv;
            $params[] = $statusCsv;
            $params[] = $statusCsv;
        }

        $leadTypeCsv = $this->normalizeCsvList($filters['deal_lead_types_csv'] ?? '');
        if ($leadTypeCsv !== '' && !$this->csvHasAll($leadTypeCsv, ['new', 'old'])) {
            $parts[] = "(
                (FIND_IN_SET('new', ?) > 0 AND EXISTS (
                    SELECT 1 FROM persons pflt
                    WHERE pflt.didar_contact_id = d.contact_didar_id
                      AND DATE(COALESCE(pflt.register_time, pflt.created_at)) = DATE(COALESCE(d.register_time, d.created_at))
                ))
                OR (FIND_IN_SET('old', ?) > 0 AND EXISTS (
                    SELECT 1 FROM persons pflt
                    WHERE pflt.didar_contact_id = d.contact_didar_id
                      AND DATE(COALESCE(pflt.register_time, pflt.created_at)) < DATE(COALESCE(d.register_time, d.created_at))
                ))
            )";
            $params[] = $leadTypeCsv;
            $params[] = $leadTypeCsv;
        }

        if ($rowOwnerDidarId !== '') {
            $parts[] = "d.owner_didar_id = ?";
            $params[] = $rowOwnerDidarId;
        }

        $whereDeals = !empty($parts) ? implode(' AND ', $parts) : '1=1';
        $failureReasonExpr = $this->buildFailureReasonExpr('d');

        // Params for INNER JOIN subquery (date_from, date_to) come FIRST
        // Then params for outer WHERE clause
        [$dateFrom, $dateTo] = $this->dateRangeFromFilters($filters);
        $paramsOut = array_merge([$dateFrom, $dateTo], $params);

        return "
            SELECT
                d.id as deal_id,
                d.didar_deal_id,
                d.title,
                d.status,
                d.pipeline_stage,
                COALESCE(d.acquaintance_source, '') as acquaintance_source,
                COALESCE(d.acquaintance_detail, '') as acquaintance_detail,
                COALESCE(d.content_topic, '') as content_topic,
                COALESCE(d.created_by_name, '') as created_by_name,
                COALESCE(d.created_via, '') as created_via,
                DATE(COALESCE(d.register_time, d.created_at)) as deal_date,
                COALESCE(d.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name,
                COALESCE(d.contact_didar_id, '') as contact_didar_id,
                COALESCE(p.sale_status, '') as lead_sale_status,
                DATE(COALESCE(p.register_time, p.created_at)) as lead_register_time,
                COALESCE(p.register_time_jalali, '') as lead_register_time_jalali,
                {$this->buildDealProductHintExpr('d')} as deal_product_hint,
                CASE
                    WHEN DATE(COALESCE(p.register_time, p.created_at)) = DATE(COALESCE(d.register_time, d.created_at)) THEN 'new'
                    ELSE 'old'
                END as lead_type,
                CASE
                    WHEN d.status = 'Won' THEN 'won'
                    WHEN d.status = 'Lost' THEN 'lost'
                    ELSE 'pending'
                END as deal_status_group,
                COALESCE(d.payable_amount, 0) as payable_amount,
                COALESCE(pay.total_paid, 0) as total_paid,
                pay.first_payment_date,
                DATE(COALESCE(d.lost_time, d.created_at)) as lost_date,
                CASE WHEN COALESCE(pay.total_paid, 0) > 0 THEN 1 ELSE 0 END as has_payment,
                CASE WHEN COALESCE(od.open_count, 0) > 0 THEN 1 ELSE 0 END as has_open_deal,
                {$failureReasonExpr} as failure_reason
            FROM deals d
            INNER JOIN (
                SELECT DISTINCT pbd.deal_id
                FROM ({$paymentsByDealDateSql}) pbd
                WHERE pbd.payment_date >= ?
                  AND pbd.payment_date <= ?
            ) pw ON pw.deal_id = d.id
            LEFT JOIN users u ON u.didar_user_id = d.owner_didar_id
            LEFT JOIN persons p ON p.didar_contact_id = d.contact_didar_id
            LEFT JOIN ({$paymentsByDealSql}) pay ON pay.deal_id = d.id
            LEFT JOIN (
                SELECT
                    d2.contact_didar_id,
                    COUNT(*) as open_count
                FROM deals d2
                WHERE d2.status NOT IN ('Won', 'Lost')
                  {$legacyOpenClause}
                GROUP BY d2.contact_didar_id
            ) od ON od.contact_didar_id = d.contact_didar_id
            WHERE {$whereDeals}
        ";
    }

    private function buildDrilldownDealsByLostDateSql(
        array $filters,
        string $rowOwnerDidarId,
        array &$paramsOut
    ): string {
        $whereDeals = $this->buildWhereClauseForLostSales($filters);
        $paramsDeals = $this->buildParamsForLostSales($filters);

        if ($rowOwnerDidarId !== '') {
            $whereDeals .= " AND d.owner_didar_id = ?";
            $paramsDeals[] = $rowOwnerDidarId;
        }

        $paymentsByDealSql = $this->buildUnifiedPaymentsByDealSql();
        $legacyOpenClause = $this->buildLegacyDealFilterClause($filters, 'd2');
        $legacyOpenClause = $legacyOpenClause !== '' ? (' AND ' . $legacyOpenClause) : '';
        $paramsOut = $paramsDeals;
        $failureReasonExpr = $this->buildFailureReasonExpr('d');

        return "
            SELECT
                d.id as deal_id,
                d.didar_deal_id,
                d.title,
                d.status,
                d.pipeline_stage,
                COALESCE(d.acquaintance_source, '') as acquaintance_source,
                COALESCE(d.acquaintance_detail, '') as acquaintance_detail,
                COALESCE(d.content_topic, '') as content_topic,
                COALESCE(d.created_by_name, '') as created_by_name,
                COALESCE(d.created_via, '') as created_via,
                DATE(COALESCE(d.register_time, d.created_at)) as deal_date,
                COALESCE(d.owner_didar_id, '') as owner_didar_id,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name,
                COALESCE(d.contact_didar_id, '') as contact_didar_id,
                COALESCE(p.sale_status, '') as lead_sale_status,
                DATE(COALESCE(p.register_time, p.created_at)) as lead_register_time,
                COALESCE(p.register_time_jalali, '') as lead_register_time_jalali,
                {$this->buildDealProductHintExpr('d')} as deal_product_hint,
                CASE
                    WHEN DATE(COALESCE(p.register_time, p.created_at)) = DATE(COALESCE(d.register_time, d.created_at)) THEN 'new'
                    ELSE 'old'
                END as lead_type,
                CASE
                    WHEN d.status = 'Won' THEN 'won'
                    WHEN d.status = 'Lost' THEN 'lost'
                    ELSE 'pending'
                END as deal_status_group,
                COALESCE(d.payable_amount, 0) as payable_amount,
                COALESCE(pay.total_paid, 0) as total_paid,
                pay.first_payment_date,
                DATE(COALESCE(d.lost_time, d.created_at)) as lost_date,
                CASE WHEN COALESCE(pay.total_paid, 0) > 0 THEN 1 ELSE 0 END as has_payment,
                CASE WHEN COALESCE(od.open_count, 0) > 0 THEN 1 ELSE 0 END as has_open_deal,
                {$failureReasonExpr} as failure_reason
            FROM deals d
            LEFT JOIN users u ON u.didar_user_id = d.owner_didar_id
            LEFT JOIN persons p ON p.didar_contact_id = d.contact_didar_id
            LEFT JOIN ({$paymentsByDealSql}) pay ON pay.deal_id = d.id
            LEFT JOIN (
                SELECT
                    d2.contact_didar_id,
                    COUNT(*) as open_count
                FROM deals d2
                WHERE d2.status NOT IN ('Won', 'Lost')
                  {$legacyOpenClause}
                GROUP BY d2.contact_didar_id
            ) od ON od.contact_didar_id = d.contact_didar_id
            WHERE d.status = 'Lost'
              AND {$whereDeals}
        ";
    }

    private function buildFailureReasonExpr(string $alias = 'd'): string
    {
        $parts = [];
        if ($this->db->hasColumn('deals', 'failure_reason_title')) {
            $parts[] = "NULLIF(TRIM({$alias}.failure_reason_title), '')";
        }
        if ($this->db->hasColumn('deals', 'failure_reason_text')) {
            $parts[] = "NULLIF(TRIM({$alias}.failure_reason_text), '')";
        }
        if ($this->db->hasColumn('deals', 'failure_reason_description')) {
            $parts[] = "NULLIF(TRIM({$alias}.failure_reason_description), '')";
        }
        if ($this->db->hasColumn('deals', 'failure_reason_code')) {
            $parts[] = "CASE TRIM({$alias}.failure_reason_code)
                WHEN 'no_financial_ability' THEN 'عدم توان مالی'
                WHEN 'no_purchase_intent' THEN 'فعلا قصد خرید ندارد'
                WHEN 'competitor_purchase' THEN 'خرید از رقبا'
                WHEN 'wrong_number' THEN 'شماره اشتباه'
                WHEN 'irrelevant_lead' THEN 'لید نامرتبط'
                WHEN 'other' THEN 'سایر'
                ELSE NULL
            END";
            $parts[] = "NULLIF(TRIM({$alias}.failure_reason_code), '')";
        }
        $parts[] = "'نامشخص'";
        return 'COALESCE(' . implode(', ', $parts) . ')';
    }

    private function buildNormalizedProductNameExpr(string $alias = 'p'): string
    {
        $baseExpr = $this->db->hasColumn('products', 'title')
            ? "COALESCE(NULLIF(TRIM({$alias}.title), ''), NULLIF(TRIM({$alias}.name), ''), '')"
            : "COALESCE(NULLIF(TRIM({$alias}.name), ''), '')";

        return "CASE
            WHEN TRIM({$baseExpr}) IN ('VIP', 'vip', 'Vip', 'کانال VIP', 'کانال vip', 'کانالvip', 'کانال  VIP') THEN 'VIP'
            WHEN TRIM({$baseExpr}) = '' THEN 'نامشخص'
            ELSE TRIM({$baseExpr})
        END";
    }

    /**
     * استخراج نشانه محصول از خود معامله:
     * 1) بخش بعد از | در عنوان معامله
     * 2) اولین مقدار requested_services
     */
    private function buildDealProductHintExpr(string $dealAlias = 'd'): string
    {
        $titlePart = "CASE
            WHEN COALESCE({$dealAlias}.title, '') <> '' AND LOCATE('|', {$dealAlias}.title) > 0
            THEN NULLIF(TRIM(SUBSTRING_INDEX({$dealAlias}.title, '|', -1)), '')
            ELSE NULL
        END";

        $requestedFirstRaw = "NULLIF(
            TRIM(BOTH '\"' FROM TRIM(BOTH ']' FROM TRIM(BOTH '[' FROM SUBSTRING_INDEX(COALESCE({$dealAlias}.requested_services, ''), ',', 1)))),
            ''
        )";

        $requestedPart = "CASE
            WHEN {$requestedFirstRaw} IS NOT NULL
                 AND {$requestedFirstRaw} NOT LIKE 'معامله %'
            THEN {$requestedFirstRaw}
            ELSE NULL
        END";

        return "COALESCE({$titlePart}, {$requestedPart})";
    }

    /**
     * نام محصول نهایی برای KPI:
     * اول hint از خود معامله، بعد snapshot ذخیره‌شده روی deal_products، و اگر نبود fallback از products.
     */
    private function buildResolvedProductNameExpr(string $hintExpr, string $fallbackAlias = 'p', string $snapshotAlias = ''): string
    {
        $fallbackExpr = $this->buildNormalizedProductNameExpr($fallbackAlias);
        $hintTrim = "TRIM(COALESCE({$hintExpr}, ''))";
        $snapshotParts = [];
        if ($snapshotAlias !== '' && $this->db->hasColumn('deal_products', 'service_title_snapshot')) {
            $snapshotParts[] = "{$snapshotAlias}.service_title_snapshot";
        }
        if ($snapshotAlias !== '' && $this->db->hasColumn('deal_products', 'product_name_snapshot')) {
            $snapshotParts[] = "{$snapshotAlias}.product_name_snapshot";
        }
        $snapshotExpr = !empty($snapshotParts)
            ? "NULLIF(TRIM(COALESCE(" . implode(', ', $snapshotParts) . ", '')), '')"
            : "NULL";

        return "CASE
            WHEN {$hintTrim} IN ('VIP', 'vip', 'Vip', 'کانال VIP', 'کانال vip', 'کانالvip', 'کانال  VIP') THEN 'VIP'
            WHEN {$hintTrim} <> '' THEN {$hintTrim}
            WHEN {$snapshotExpr} IS NOT NULL THEN {$snapshotExpr}
            ELSE {$fallbackExpr}
        END";
    }

    /**
     * انتخاب محصول اصلی هر معامله:
     * آخرین سطر ثبت‌شده در deal_products (created_at جدیدتر، سپس id بزرگ‌تر)
     */
    private function buildPrimaryDealProductSql(): string
    {
        $snapshotSelects = [];
        $snapshotSelects[] = $this->db->hasColumn('deal_products', 'product_name_snapshot')
            ? "dp1.product_name_snapshot"
            : "NULL AS product_name_snapshot";
        $snapshotSelects[] = $this->db->hasColumn('deal_products', 'service_title_snapshot')
            ? "dp1.service_title_snapshot"
            : "NULL AS service_title_snapshot";
        $snapshotSelects[] = $this->db->hasColumn('deal_products', 'service_lookup_item_id_snapshot')
            ? "dp1.service_lookup_item_id_snapshot"
            : "NULL AS service_lookup_item_id_snapshot";

        return "
            SELECT
                dp1.deal_id,
                dp1.product_id,
                " . implode(",\n                ", $snapshotSelects) . "
            FROM deal_products dp1
            INNER JOIN (
                SELECT deal_id, MAX(id) as max_id
                FROM deal_products
                GROUP BY deal_id
            ) latest ON latest.deal_id = dp1.deal_id AND latest.max_id = dp1.id
        ";
    }

    /**
     * ساخت where clause بر اساس فیلترها
     */
    private function buildWhereClause(array $filters): string
    {
        return $this->buildWhereClauseForDeals($filters, 'd');
    }

    /**
     * ساخت where clause برای فیلترهای مبتنی بر معامله
     */
    private function buildWhereClauseForDeals(array $filters, string $alias = 'd'): string
    {
        $parts = [];
        $statusCsv = $this->normalizeCsvList($filters['deal_statuses_csv'] ?? '');
        $leadTypeCsv = $this->normalizeCsvList($filters['deal_lead_types_csv'] ?? '');

        $legacyClause = $this->buildLegacyDealFilterClause($filters, $alias);
        if ($legacyClause !== '') {
            $parts[] = $legacyClause;
        }

        if (!empty($filters['date_from'])) {
            $parts[] = "DATE(COALESCE({$alias}.register_time, {$alias}.created_at)) >= ?";
        }
        if (!empty($filters['date_to'])) {
            $parts[] = "DATE(COALESCE({$alias}.register_time, {$alias}.created_at)) <= ?";
        }

        if (!empty($filters['owner_didar_ids_csv'])) {
            $parts[] = "FIND_IN_SET({$alias}.owner_didar_id, ?)";
        } elseif (!empty($filters['owner_didar_id']) && trim($filters['owner_didar_id']) !== '') {
            $parts[] = "{$alias}.owner_didar_id = ?";
        }

        if (!empty($filters['product_ids_csv'])) {
            $parts[] = "EXISTS (SELECT 1 FROM deal_products dp WHERE dp.deal_id = {$alias}.id AND FIND_IN_SET(dp.product_id, ?))";
        } elseif (!empty($filters['product_id'])) {
            $parts[] = "EXISTS (SELECT 1 FROM deal_products dp WHERE dp.deal_id = {$alias}.id AND dp.product_id = ?)";
        }

        if ($statusCsv !== '' && !$this->csvHasAll($statusCsv, ['pending', 'won', 'lost'])) {
            $parts[] = "(
                (FIND_IN_SET('pending', ?) > 0 AND {$alias}.status NOT IN ('Won', 'Lost'))
                OR (FIND_IN_SET('won', ?) > 0 AND {$alias}.status = 'Won')
                OR (FIND_IN_SET('lost', ?) > 0 AND {$alias}.status = 'Lost')
            )";
        }

        if ($leadTypeCsv !== '' && !$this->csvHasAll($leadTypeCsv, ['new', 'old'])) {
            $parts[] = "(
                (FIND_IN_SET('new', ?) > 0 AND EXISTS (
                    SELECT 1 FROM persons pflt
                    WHERE pflt.didar_contact_id = {$alias}.contact_didar_id
                      AND DATE(COALESCE(pflt.register_time, pflt.created_at)) = DATE(COALESCE({$alias}.register_time, {$alias}.created_at))
                ))
                OR (FIND_IN_SET('old', ?) > 0 AND EXISTS (
                    SELECT 1 FROM persons pflt
                    WHERE pflt.didar_contact_id = {$alias}.contact_didar_id
                      AND DATE(COALESCE(pflt.register_time, pflt.created_at)) < DATE(COALESCE({$alias}.register_time, {$alias}.created_at))
                ))
            )";
        }

        return !empty($parts) ? implode(' AND ', $parts) : '1=1';
    }

    /**
     * همان where معاملات، بدون محدودیت تاریخ ثبت معامله
     * (برای سناریوهایی که تاریخ باید از پرداخت گرفته شود)
     */
    private function buildWhereClauseForDealsNoDate(array $filters, string $alias = 'd'): string
    {
        $parts = [];
        $statusCsv = $this->normalizeCsvList($filters['deal_statuses_csv'] ?? '');
        $leadTypeCsv = $this->normalizeCsvList($filters['deal_lead_types_csv'] ?? '');

        $legacyClause = $this->buildLegacyDealFilterClause($filters, $alias);
        if ($legacyClause !== '') {
            $parts[] = $legacyClause;
        }

        if (!empty($filters['owner_didar_ids_csv'])) {
            $parts[] = "FIND_IN_SET({$alias}.owner_didar_id, ?)";
        } elseif (!empty($filters['owner_didar_id']) && trim($filters['owner_didar_id']) !== '') {
            $parts[] = "{$alias}.owner_didar_id = ?";
        }

        if (!empty($filters['product_ids_csv'])) {
            $parts[] = "EXISTS (SELECT 1 FROM deal_products dp WHERE dp.deal_id = {$alias}.id AND FIND_IN_SET(dp.product_id, ?))";
        } elseif (!empty($filters['product_id'])) {
            $parts[] = "EXISTS (SELECT 1 FROM deal_products dp WHERE dp.deal_id = {$alias}.id AND dp.product_id = ?)";
        }

        if ($statusCsv !== '' && !$this->csvHasAll($statusCsv, ['pending', 'won', 'lost'])) {
            $parts[] = "(
                (FIND_IN_SET('pending', ?) > 0 AND {$alias}.status NOT IN ('Won', 'Lost'))
                OR (FIND_IN_SET('won', ?) > 0 AND {$alias}.status = 'Won')
                OR (FIND_IN_SET('lost', ?) > 0 AND {$alias}.status = 'Lost')
            )";
        }

        if ($leadTypeCsv !== '' && !$this->csvHasAll($leadTypeCsv, ['new', 'old'])) {
            $parts[] = "(
                (FIND_IN_SET('new', ?) > 0 AND EXISTS (
                    SELECT 1 FROM persons pflt
                    WHERE pflt.didar_contact_id = {$alias}.contact_didar_id
                      AND DATE(COALESCE(pflt.register_time, pflt.created_at)) = DATE(COALESCE({$alias}.register_time, {$alias}.created_at))
                ))
                OR (FIND_IN_SET('old', ?) > 0 AND EXISTS (
                    SELECT 1 FROM persons pflt
                    WHERE pflt.didar_contact_id = {$alias}.contact_didar_id
                      AND DATE(COALESCE(pflt.register_time, pflt.created_at)) < DATE(COALESCE({$alias}.register_time, {$alias}.created_at))
                ))
            )";
        }

        return !empty($parts) ? implode(' AND ', $parts) : '1=1';
    }

    /**
     * ساخت where clause برای Sales (بدون محدودیت status معامله)
     */
    private function buildWhereClauseForSales(array $filters): string
    {
        return $this->buildWhereClauseForDeals($filters, 'd');
    }

    private function getLegacyDealScope(array $filters): string
    {
        $scope = strtolower(trim((string)($filters['legacy_deal_scope'] ?? 'exclude')));
        return in_array($scope, ['exclude', 'only', 'all'], true) ? $scope : 'exclude';
    }

    private function buildLegacyDealFilterClause(array $filters, string $alias = 'd'): string
    {
        if (!$this->db->hasColumn('deals', 'is_legacy_didar')) {
            return '';
        }

        $scope = $this->getLegacyDealScope($filters);
        if ($scope === 'all') {
            return '';
        }

        return $scope === 'only'
            ? "COALESCE({$alias}.is_legacy_didar, 0) = 1"
            : "COALESCE({$alias}.is_legacy_didar, 0) = 0";
    }

    /**
     * ساخت where clause برای Lost Sales
     */
    private function buildWhereClauseForLostSales(array $filters): string
    {
        $parts = [];
        $statusCsv = $this->normalizeCsvList($filters['deal_statuses_csv'] ?? '');
        $leadTypeCsv = $this->normalizeCsvList($filters['deal_lead_types_csv'] ?? '');

        $legacyClause = $this->buildLegacyDealFilterClause($filters, 'd');
        if ($legacyClause !== '') {
            $parts[] = $legacyClause;
        }

        // فیلتر زمانی بر اساس lost_time یا fallback به register_time
        if (!empty($filters['date_from'])) {
            $parts[] = "DATE(COALESCE(d.lost_time, d.register_time, d.created_at)) >= ?";
        }
        if (!empty($filters['date_to'])) {
            $parts[] = "DATE(COALESCE(d.lost_time, d.register_time, d.created_at)) <= ?";
        }

        // فیلتر کاربر
        if (!empty($filters['owner_didar_ids_csv'])) {
            $parts[] = "FIND_IN_SET(d.owner_didar_id, ?)";
        } elseif (!empty($filters['owner_didar_id'])) {
            $parts[] = "d.owner_didar_id = ?";
        }

        // فیلتر محصول
        if (!empty($filters['product_ids_csv'])) {
            $parts[] = "EXISTS (SELECT 1 FROM deal_products dp WHERE dp.deal_id = d.id AND FIND_IN_SET(dp.product_id, ?))";
        } elseif (!empty($filters['product_id'])) {
            $parts[] = "EXISTS (SELECT 1 FROM deal_products dp WHERE dp.deal_id = d.id AND dp.product_id = ?)";
        }

        if ($statusCsv !== '' && !$this->csvHasValue($statusCsv, 'lost')) {
            $parts[] = "1 = 0";
        }

        if ($leadTypeCsv !== '' && !$this->csvHasAll($leadTypeCsv, ['new', 'old'])) {
            $parts[] = "(
                (FIND_IN_SET('new', ?) > 0 AND EXISTS (
                    SELECT 1 FROM persons pflt
                    WHERE pflt.didar_contact_id = d.contact_didar_id
                      AND DATE(COALESCE(pflt.register_time, pflt.created_at)) = DATE(COALESCE(d.register_time, d.created_at))
                ))
                OR (FIND_IN_SET('old', ?) > 0 AND EXISTS (
                    SELECT 1 FROM persons pflt
                    WHERE pflt.didar_contact_id = d.contact_didar_id
                      AND DATE(COALESCE(pflt.register_time, pflt.created_at)) < DATE(COALESCE(d.register_time, d.created_at))
                ))
            )";
        }

        return !empty($parts) ? implode(' AND ', $parts) : '1=1';
    }

    /**
     * ساخت where clause بدون transactions (برای Open Pipeline و Persons)
     */
    private function buildWhereClauseWithoutTransactions(array $filters): string
    {
        return $this->buildWhereClauseForDeals($filters, 'd');
    }

    /**
     * ساخت where clause برای Persons
     * NOTE: persons جدول product_id ندارد
     */
    private function buildWhereClauseForPersons(array $filters): string
    {
        $parts = [];

        // فیلتر زمانی
        if (!empty($filters['date_from'])) {
            $parts[] = "DATE(p.register_time) >= ?";
        }
        if (!empty($filters['date_to'])) {
            $parts[] = "DATE(p.register_time) <= ?";
        }

        // فیلتر کاربر
        if (!empty($filters['owner_didar_ids_csv'])) {
            $parts[] = "FIND_IN_SET(p.owner_didar_id, ?)";
        } elseif (!empty($filters['owner_didar_id'])) {
            $parts[] = "p.owner_didar_id = ?";
        }

        // نکته: product_id را برای persons نادیده می‌گیریم چون persons جدول product_id ندارد

        return !empty($parts) ? implode(' AND ', $parts) : '1=1';
    }

    /**
     * ساخت where clause برای جزئیات معاملات KPI
     * فیلتر تاریخ بر اساس اولین پرداخت (t5)
     */
    private function buildWhereClauseForDealDetails(array $filters): string
    {
        $parts = [];
        $statusCsv = $this->normalizeCsvList($filters['deal_statuses_csv'] ?? '');
        $leadTypeCsv = $this->normalizeCsvList($filters['deal_lead_types_csv'] ?? '');

        $legacyClause = $this->buildLegacyDealFilterClause($filters, 'd');
        if ($legacyClause !== '') {
            $parts[] = $legacyClause;
        }

        if (!empty($filters['date_from'])) {
            $parts[] = "DATE(COALESCE(d.register_time, d.created_at)) >= ?";
        }
        if (!empty($filters['date_to'])) {
            $parts[] = "DATE(COALESCE(d.register_time, d.created_at)) <= ?";
        }
        if (!empty($filters['owner_didar_ids_csv'])) {
            $parts[] = "FIND_IN_SET(d.owner_didar_id, ?)";
        } elseif (!empty($filters['owner_didar_id'])) {
            $parts[] = "d.owner_didar_id = ?";
        }
        if (!empty($filters['product_ids_csv'])) {
            $parts[] = "EXISTS (SELECT 1 FROM deal_products dp WHERE dp.deal_id = d.id AND FIND_IN_SET(dp.product_id, ?))";
        } elseif (!empty($filters['product_id'])) {
            $parts[] = "EXISTS (SELECT 1 FROM deal_products dp WHERE dp.deal_id = d.id AND dp.product_id = ?)";
        }

        if ($statusCsv !== '' && !$this->csvHasAll($statusCsv, ['pending', 'won', 'lost'])) {
            $parts[] = "(
                (FIND_IN_SET('pending', ?) > 0 AND d.status NOT IN ('Won', 'Lost'))
                OR (FIND_IN_SET('won', ?) > 0 AND d.status = 'Won')
                OR (FIND_IN_SET('lost', ?) > 0 AND d.status = 'Lost')
            )";
        }

        if ($leadTypeCsv !== '' && !$this->csvHasAll($leadTypeCsv, ['new', 'old'])) {
            $parts[] = "(
                (FIND_IN_SET('new', ?) > 0 AND EXISTS (
                    SELECT 1 FROM persons pflt
                    WHERE pflt.didar_contact_id = d.contact_didar_id
                      AND DATE(COALESCE(pflt.register_time, pflt.created_at)) = DATE(COALESCE(d.register_time, d.created_at))
                ))
                OR (FIND_IN_SET('old', ?) > 0 AND EXISTS (
                    SELECT 1 FROM persons pflt
                    WHERE pflt.didar_contact_id = d.contact_didar_id
                      AND DATE(COALESCE(pflt.register_time, pflt.created_at)) < DATE(COALESCE(d.register_time, d.created_at))
                ))
            )";
        }

        return !empty($parts) ? implode(' AND ', $parts) : '1=1';
    }

    private function buildWhereClauseForDailySales(array $filters): string
    {
        return $this->buildWhereClauseForDeals($filters, 'd');
    }

    /**
     * ساخت پارامترها برای کوئری
     */
    private function buildParams(array $filters): array
    {
        return $this->buildParamsForDeals($filters);
    }

    /**
     * ساخت پارامترها برای Sales
     */
    private function buildParamsForSales(array $filters): array
    {
        return $this->buildParamsForDeals($filters);
    }

    /**
     * ساخت پارامترها برای Lost Sales
     */
    private function buildParamsForLostSales(array $filters): array
    {
        $params = [];
        $statusCsv = $this->normalizeCsvList($filters['deal_statuses_csv'] ?? '');
        $leadTypeCsv = $this->normalizeCsvList($filters['deal_lead_types_csv'] ?? '');

        if (!empty($filters['date_from'])) {
            $params[] = $filters['date_from'];
        }
        if (!empty($filters['date_to'])) {
            $params[] = $filters['date_to'];
        }
        if (!empty($filters['owner_didar_ids_csv'])) {
            $params[] = $filters['owner_didar_ids_csv'];
        } elseif (!empty($filters['owner_didar_id'])) {
            $params[] = $filters['owner_didar_id'];
        }
        if (!empty($filters['product_ids_csv'])) {
            $params[] = $filters['product_ids_csv'];
        } elseif (!empty($filters['product_id'])) {
            $params[] = $filters['product_id'];
        }
        if ($leadTypeCsv !== '' && !$this->csvHasAll($leadTypeCsv, ['new', 'old'])) {
            $params[] = $leadTypeCsv;
            $params[] = $leadTypeCsv;
        }

        return $params;
    }

    /**
     * ساخت پارامترها بدون transactions
     */
    private function buildParamsWithoutTransactions(array $filters): array
    {
        return $this->buildParamsForDeals($filters);
    }

    /**
     * ساخت پارامترها برای فیلترهای مبتنی بر معامله
     */
    private function buildParamsForDeals(array $filters): array
    {
        $params = [];
        $statusCsv = $this->normalizeCsvList($filters['deal_statuses_csv'] ?? '');
        $leadTypeCsv = $this->normalizeCsvList($filters['deal_lead_types_csv'] ?? '');

        if (!empty($filters['date_from'])) {
            $params[] = $filters['date_from'];
        }
        if (!empty($filters['date_to'])) {
            $params[] = $filters['date_to'];
        }
        if (!empty($filters['owner_didar_ids_csv'])) {
            $params[] = $filters['owner_didar_ids_csv'];
        } elseif (!empty($filters['owner_didar_id']) && trim($filters['owner_didar_id']) !== '') {
            $params[] = trim($filters['owner_didar_id']);
        }
        if (!empty($filters['product_ids_csv'])) {
            $params[] = $filters['product_ids_csv'];
        } elseif (!empty($filters['product_id'])) {
            $params[] = $filters['product_id'];
        }
        if ($statusCsv !== '' && !$this->csvHasAll($statusCsv, ['pending', 'won', 'lost'])) {
            $params[] = $statusCsv;
            $params[] = $statusCsv;
            $params[] = $statusCsv;
        }
        if ($leadTypeCsv !== '' && !$this->csvHasAll($leadTypeCsv, ['new', 'old'])) {
            $params[] = $leadTypeCsv;
            $params[] = $leadTypeCsv;
        }

        return $params;
    }

    /**
     * پارامترهای where بدون محدودیت تاریخ ثبت معامله
     */
    private function buildParamsForDealsNoDate(array $filters): array
    {
        $params = [];
        $statusCsv = $this->normalizeCsvList($filters['deal_statuses_csv'] ?? '');
        $leadTypeCsv = $this->normalizeCsvList($filters['deal_lead_types_csv'] ?? '');

        if (!empty($filters['owner_didar_ids_csv'])) {
            $params[] = $filters['owner_didar_ids_csv'];
        } elseif (!empty($filters['owner_didar_id']) && trim($filters['owner_didar_id']) !== '') {
            $params[] = trim($filters['owner_didar_id']);
        }
        if (!empty($filters['product_ids_csv'])) {
            $params[] = $filters['product_ids_csv'];
        } elseif (!empty($filters['product_id'])) {
            $params[] = $filters['product_id'];
        }
        if ($statusCsv !== '' && !$this->csvHasAll($statusCsv, ['pending', 'won', 'lost'])) {
            $params[] = $statusCsv;
            $params[] = $statusCsv;
            $params[] = $statusCsv;
        }
        if ($leadTypeCsv !== '' && !$this->csvHasAll($leadTypeCsv, ['new', 'old'])) {
            $params[] = $leadTypeCsv;
            $params[] = $leadTypeCsv;
        }

        return $params;
    }

    /**
     * ساخت پارامترها برای Persons
     */
    private function buildParamsForPersons(array $filters): array
    {
        $params = [];

        if (!empty($filters['date_from'])) {
            $params[] = $filters['date_from'];
        }
        if (!empty($filters['date_to'])) {
            $params[] = $filters['date_to'];
        }
        if (!empty($filters['owner_didar_ids_csv'])) {
            $params[] = $filters['owner_didar_ids_csv'];
        } elseif (!empty($filters['owner_didar_id'])) {
            $params[] = $filters['owner_didar_id'];
        }

        return $params;
    }

    private function getKpiPaymentDateBasis(array $filters, string $scope = 'payment'): string
    {
        $key = ($scope === 'sales') ? 'kpi_sales_date_basis' : 'kpi_payment_date_basis';
        $value = strtolower(trim((string)($filters[$key] ?? '')));
        return in_array($value, ['ui_payment_date', 'db_payment_date', 'system_created_at'], true)
            ? $value
            : 'ui_payment_date';
    }

    private function getKpiPaymentCountRule(array $filters, string $scope = 'payment'): string
    {
        $key = ($scope === 'sales') ? 'kpi_sales_count_rule' : 'kpi_payment_count_rule';
        $value = strtolower(trim((string)($filters[$key] ?? '')));
        return in_array($value, ['all_payments', 'first_payment_only'], true)
            ? $value
            : ($scope === 'sales' ? 'first_payment_only' : 'all_payments');
    }

    private function getKpiPaymentStatusScope(array $filters): string
    {
        $value = strtolower(trim((string)($filters['kpi_payment_status_scope'] ?? '')));
        return in_array($value, ['confirmed_only', 'all_statuses'], true)
            ? $value
            : 'confirmed_only';
    }

    private function buildKpiPaymentsByDealDateSql(array $filters, string $scope = 'payment'): string
    {
        $dateBasis = $this->getKpiPaymentDateBasis($filters, $scope);
        $statusScope = $this->getKpiPaymentStatusScope($filters);

        if ($dateBasis === 'db_payment_date' || $dateBasis === 'system_created_at') {
            $dateExpr = $dateBasis === 'system_created_at'
                ? 'DATE(t.created_at)'
                : "DATE(CASE WHEN LEFT(COALESCE(t.payment_date, ''), 10) = '0000-00-00' THEN NULL ELSE t.payment_date END)";
            $statusClause = $statusScope === 'confirmed_only'
                ? " AND LOWER(COALESCE(t.status, '')) = 'confirmed'"
                : '';

            return "
                SELECT
                    t.deal_id,
                    {$dateExpr} as payment_date,
                    SUM(COALESCE(t.amount, 0)) as amount
                FROM transactions t
                WHERE COALESCE(t.amount, 0) > 0
                  {$statusClause}
                GROUP BY t.deal_id, {$dateExpr}
                HAVING payment_date IS NOT NULL
            ";
        }

        return $this->buildUnifiedPaymentsByDealDateSqlForUiBasis($filters);
    }

    private function buildKpiSalesPaymentsByDealDateSql(array $filters): string
    {
        $countRule = $this->getKpiPaymentCountRule($filters, 'sales');
        if ($countRule === 'all_payments') {
            return $this->buildKpiPaymentsByDealDateSql($filters, 'sales');
        }
        return $this->buildKpiFirstPaymentDayByDealSql($filters, 'sales');
    }

    private function buildKpiPaymentsWindowByDealDateSql(array $filters, string $scope = 'payment'): string
    {
        $countRule = $this->getKpiPaymentCountRule($filters, $scope);
        if ($countRule === 'first_payment_only') {
            return $this->buildKpiFirstPaymentDayByDealSql($filters, $scope);
        }
        return $this->buildKpiPaymentsByDealDateSql($filters, $scope);
    }

    private function buildKpiFirstPaymentDayByDealSql(array $filters, string $scope = 'sales'): string
    {
        $paymentsByDealDateSql = $this->buildKpiPaymentsByDealDateSql($filters, $scope);
        return "
            SELECT
                p.deal_id,
                p.payment_date,
                p.amount
            FROM ({$paymentsByDealDateSql}) p
            INNER JOIN (
                SELECT deal_id, MIN(payment_date) as first_payment_date
                FROM ({$paymentsByDealDateSql}) fp
                GROUP BY deal_id
            ) f ON f.deal_id = p.deal_id AND f.first_payment_date = p.payment_date
        ";
    }

    private function buildKpiPaymentsByDealSql(array $filters, string $scope = 'payment', ?string $forceCountRule = null): string
    {
        $countRule = $forceCountRule ?? $this->getKpiPaymentCountRule($filters, $scope);
        $paymentsByDealDateSql = ($countRule === 'first_payment_only')
            ? $this->buildKpiFirstPaymentDayByDealSql($filters, $scope)
            : $this->buildKpiPaymentsByDealDateSql($filters, $scope);
        return "
            SELECT
                p.deal_id,
                SUM(p.amount) as total_paid,
                MIN(p.payment_date) as first_payment_date
            FROM ({$paymentsByDealDateSql}) p
            GROUP BY p.deal_id
        ";
    }

    /**
     * جمع کل پرداخت‌های هر معامله (همه‌زمان) برای KPIهای مالی.
     * در مبنای UI، مبلغ‌ها از JSON معامله خوانده می‌شوند (حتی اگر فقط date_persian داشته باشند)
     * و فقط وقتی JSON پرداخت نداشته باشد از transactions fallback می‌شود.
     * هدف: جلوگیری از double-count و همزمان از دست ندادن مبلغ‌های JSON با date_persian-only.
     */
    private function buildKpiAllTimePaymentTotalsByDealSql(array $filters, string $scope = 'payment'): string
    {
        $dateBasis = $this->getKpiPaymentDateBasis($filters, $scope);
        if ($dateBasis !== 'ui_payment_date') {
            return $this->buildKpiPaymentsByDealSql($filters, $scope, 'all_payments');
        }

        $statusScope = $this->getKpiPaymentStatusScope($filters);
        $statusClause = $statusScope === 'confirmed_only'
            ? " AND LOWER(COALESCE(t.status, '')) = 'confirmed'"
            : '';

        return "
            SELECT
                p.deal_id,
                SUM(COALESCE(p.amount, 0)) as total_paid,
                NULL as first_payment_date
            FROM (
                SELECT
                    d.id as deal_id,
                    CAST(
                        COALESCE(
                            NULLIF(JSON_UNQUOTE(JSON_EXTRACT(j.payment_obj, '$.amount')), ''),
                            '0'
                        ) AS DECIMAL(15,2)
                    ) as amount
                FROM deals d
                JOIN JSON_TABLE(
                    CASE
                        WHEN JSON_VALID(d.payments) THEN d.payments
                        ELSE JSON_ARRAY()
                    END,
                    '$[*]' COLUMNS (
                        payment_obj JSON PATH '$'
                    )
                ) j
                WHERE COALESCE(d.payments, '') <> ''
                  AND JSON_VALID(d.payments)

                UNION ALL

                SELECT
                    t.deal_id,
                    COALESCE(t.amount, 0) as amount
                FROM transactions t
                WHERE COALESCE(t.amount, 0) > 0
                  {$statusClause}
                  AND NOT EXISTS (
                    SELECT 1
                    FROM deals d2
                    WHERE d2.id = t.deal_id
                      AND COALESCE(d2.payments, '') <> ''
                      AND JSON_VALID(d2.payments)
                      AND COALESCE(JSON_LENGTH(d2.payments), 0) > 0
                  )
            ) p
            WHERE COALESCE(p.amount, 0) > 0
            GROUP BY p.deal_id
        ";
    }

    /**
     * وجود مطلق هرگونه پرداخت برای هر معامله (مستقل از بازه زمانی، مبنای تاریخ و وضعیت پرداخت).
     * برای KPI «معاملات بدون پرداخت» استفاده می‌شود تا فقط معاملاتی که واقعاً هیچ پرداختی ندارند برگردند.
     */
    private function buildAbsolutePaymentPresenceByDealSql(): string
    {
        return "
            SELECT
                p.deal_id,
                SUM(p.amount) as total_paid,
                NULL as first_payment_date
            FROM (
                SELECT
                    t.deal_id,
                    COALESCE(t.amount, 0) as amount
                FROM transactions t
                WHERE COALESCE(t.amount, 0) > 0

                UNION ALL

                SELECT
                    d.id as deal_id,
                    CAST(
                        COALESCE(
                            NULLIF(JSON_UNQUOTE(JSON_EXTRACT(j.payment_obj, '$.amount')), ''),
                            '0'
                        ) AS DECIMAL(15,2)
                    ) as amount
                FROM deals d
                JOIN JSON_TABLE(
                    CASE
                        WHEN JSON_VALID(d.payments) THEN d.payments
                        ELSE JSON_ARRAY()
                    END,
                    '$[*]' COLUMNS (
                        payment_obj JSON PATH '$'
                    )
                ) j
                WHERE COALESCE(d.payments, '') <> ''
                  AND JSON_VALID(d.payments)
            ) p
            WHERE COALESCE(p.amount, 0) > 0
            GROUP BY p.deal_id
        ";
    }

    private function buildUnifiedPaymentsByDealDateSqlForUiBasis(array $filters): string
    {
        $statusScope = $this->getKpiPaymentStatusScope($filters);
        $statusClause = $statusScope === 'confirmed_only'
            ? " AND LOWER(COALESCE(t.status, '')) = 'confirmed'"
            : '';
        return "
            SELECT
                src.deal_id,
                src.payment_date,
                SUM(src.amount) as amount
            FROM (
                SELECT
                    d.id as deal_id,
                    DATE(
                        CASE
                            WHEN LEFT(
                                COALESCE(
                                    NULLIF(TRIM(JSON_UNQUOTE(JSON_EXTRACT(j.payment_obj, '$.date'))), ''),
                                    NULLIF(TRIM(JSON_UNQUOTE(JSON_EXTRACT(j.payment_obj, '$.payment_date'))), '')
                                ),
                                10
                            ) = '0000-00-00'
                            THEN NULL
                            ELSE COALESCE(
                                NULLIF(TRIM(JSON_UNQUOTE(JSON_EXTRACT(j.payment_obj, '$.date'))), ''),
                                NULLIF(TRIM(JSON_UNQUOTE(JSON_EXTRACT(j.payment_obj, '$.payment_date'))), '')
                            )
                        END
                    ) as payment_date,
                    CAST(
                        COALESCE(
                            NULLIF(JSON_UNQUOTE(JSON_EXTRACT(j.payment_obj, '$.amount')), ''),
                            '0'
                        ) AS DECIMAL(15,2)
                    ) as amount
                FROM deals d
                JOIN JSON_TABLE(
                    CASE
                        WHEN JSON_VALID(d.payments) THEN d.payments
                        ELSE JSON_ARRAY()
                    END,
                    '$[*]' COLUMNS (
                        payment_obj JSON PATH '$'
                    )
                ) j
                WHERE COALESCE(d.payments, '') <> ''
                  AND JSON_VALID(d.payments)

                UNION ALL

                SELECT
                    t.deal_id,
                    DATE(CASE WHEN LEFT(COALESCE(t.payment_date, ''), 10) = '0000-00-00' THEN NULL ELSE t.payment_date END) as payment_date,
                    COALESCE(t.amount, 0) as amount
                FROM transactions t
                WHERE COALESCE(t.amount, 0) > 0
                  {$statusClause}
                  AND NOT EXISTS (
                    SELECT 1
                    FROM deals d2
                    WHERE d2.id = t.deal_id
                      AND COALESCE(d2.payments, '') <> ''
                      AND JSON_VALID(d2.payments)
                      AND COALESCE(JSON_LENGTH(d2.payments), 0) > 0
                  )
            ) src
            WHERE src.payment_date IS NOT NULL
              AND COALESCE(src.amount, 0) > 0
            GROUP BY src.deal_id, src.payment_date
        ";
    }

    /**
     * پرداخت‌های یکپارچه بر اساس معامله + تاریخ پرداخت.
     * اولویت با جدول transactions است؛ اگر معامله هیچ تراکنشی نداشت،
     * پرداخت‌های legacy از deals.payments استخراج می‌شوند.
     */
    private function buildUnifiedPaymentsByDealDateSql(): string
    {
        return "
            SELECT
                src.deal_id,
                src.payment_date,
                SUM(src.amount) as amount
            FROM (
                SELECT
                    d.id as deal_id,
                    DATE(
                        CASE
                            WHEN LEFT(
                                COALESCE(
                                    NULLIF(TRIM(JSON_UNQUOTE(JSON_EXTRACT(j.payment_obj, '$.date'))), ''),
                                    NULLIF(TRIM(JSON_UNQUOTE(JSON_EXTRACT(j.payment_obj, '$.payment_date'))), '')
                                ),
                                10
                            ) = '0000-00-00'
                            THEN NULL
                            ELSE COALESCE(
                                NULLIF(TRIM(JSON_UNQUOTE(JSON_EXTRACT(j.payment_obj, '$.date'))), ''),
                                NULLIF(TRIM(JSON_UNQUOTE(JSON_EXTRACT(j.payment_obj, '$.payment_date'))), '')
                            )
                        END
                    ) as payment_date,
                    CAST(
                        COALESCE(
                            NULLIF(JSON_UNQUOTE(JSON_EXTRACT(j.payment_obj, '$.amount')), ''),
                            '0'
                        ) AS DECIMAL(15,2)
                    ) as amount
                FROM deals d
                JOIN JSON_TABLE(
                    CASE
                        WHEN JSON_VALID(d.payments) THEN d.payments
                        ELSE JSON_ARRAY()
                    END,
                    '$[*]' COLUMNS (
                        payment_obj JSON PATH '$'
                    )
                ) j
                WHERE COALESCE(d.payments, '') <> ''
                  AND JSON_VALID(d.payments)

                UNION ALL

                SELECT
                    t.deal_id,
                    DATE(CASE WHEN LEFT(COALESCE(t.payment_date, ''), 10) = '0000-00-00' THEN NULL ELSE t.payment_date END) as payment_date,
                    COALESCE(t.amount, 0) as amount
                FROM transactions t
                WHERE COALESCE(t.amount, 0) > 0
                  AND NOT EXISTS (
                    SELECT 1
                    FROM deals d2
                    JOIN JSON_TABLE(
                        CASE
                            WHEN JSON_VALID(d2.payments) THEN d2.payments
                            ELSE JSON_ARRAY()
                        END,
                        '$[*]' COLUMNS (
                            json_date VARCHAR(64) PATH '$.date',
                            json_payment_date VARCHAR(64) PATH '$.payment_date'
                        )
                    ) jt2
                    WHERE d2.id = t.deal_id
                      AND NOT (
                          LEFT(COALESCE(NULLIF(TRIM(jt2.json_date), ''), NULLIF(TRIM(jt2.json_payment_date), '')), 10) = '0000-00-00'
                      )
                      AND COALESCE(
                          NULLIF(TRIM(jt2.json_date), ''),
                          NULLIF(TRIM(jt2.json_payment_date), '')
                      ) IS NOT NULL
                  )
            ) src
            WHERE src.payment_date IS NOT NULL
              AND COALESCE(src.amount, 0) > 0
            GROUP BY src.deal_id, src.payment_date
        ";
    }

    /**
     * فقط روز/مبلغ اولین پرداخت هر معامله (برای KPIهای فروش‌محور مبتنی بر «اولین فروش»)
     * اگر چند پرداخت در همان روز اول ثبت شده باشند، مبلغ همان روز تجمیع می‌شود.
     */
    private function buildUnifiedFirstPaymentDayByDealSql(): string
    {
        $paymentsByDealDateSql = $this->buildUnifiedPaymentsByDealDateSql();
        return "
            SELECT
                p.deal_id,
                p.payment_date,
                p.amount
            FROM ({$paymentsByDealDateSql}) p
            INNER JOIN (
                SELECT deal_id, MIN(payment_date) as first_payment_date
                FROM ({$paymentsByDealDateSql}) fp
                GROUP BY deal_id
            ) f ON f.deal_id = p.deal_id AND f.first_payment_date = p.payment_date
        ";
    }

    /**
     * تجمیع پرداخت یکپارچه به ازای هر معامله.
     */
    private function buildUnifiedPaymentsByDealSql(): string
    {
        $paymentsByDealDateSql = $this->buildUnifiedPaymentsByDealDateSql();
        return "
            SELECT
                p.deal_id,
                SUM(p.amount) as total_paid,
                MIN(p.payment_date) as first_payment_date
            FROM ({$paymentsByDealDateSql}) p
            GROUP BY p.deal_id
        ";
    }

    private function getPaymentWindowDealClassificationUiOracle(array $filters): array
    {
        $oracle = $this->getUiOracleKpiData($filters);
        $row = $oracle['financial_window'] ?? [];
        return [
            'paid_deals_count' => (int)($row['paid_deals_count'] ?? 0),
            'paid_deals_contract_sum' => (float)($row['paid_deals_contract_sum'] ?? 0),
            'paid_deals_collected_sum' => (float)($row['paid_deals_collected_sum'] ?? 0),
            'outstanding_count' => (int)($row['outstanding_count'] ?? 0),
            'outstanding_amount_sum' => (float)($row['outstanding_amount_sum'] ?? 0),
            'outstanding_contract_sum' => (float)($row['outstanding_contract_sum'] ?? 0),
            'outstanding_collected_sum' => (float)($row['outstanding_collected_sum'] ?? 0),
            'settled_count' => (int)($row['settled_count'] ?? 0),
            'settled_contract_sum' => (float)($row['settled_contract_sum'] ?? 0),
            'settled_collected_sum' => (float)($row['settled_collected_sum'] ?? 0),
        ];
    }

    private function getDealConversionRateUiOracle(array $filters): array
    {
        $oracle = $this->getUiOracleKpiData($filters);
        $result = $oracle['deal_conversion'] ?? ['total_deals' => 0, 'converted_deals' => 0];
        $totalDeals = (int)($result['total_deals'] ?? 0);
        $convertedDeals = (int)($result['converted_deals'] ?? 0);
        $conversionRate = $totalDeals > 0 ? round(($convertedDeals / $totalDeals) * 100, 2) : 0;
        $nonConversionRate = $totalDeals > 0 ? round((($totalDeals - $convertedDeals) / $totalDeals) * 100, 2) : 0;
        return [
            'total_deals' => $totalDeals,
            'converted_deals' => $convertedDeals,
            'conversion_rate' => $conversionRate,
            'non_conversion_rate' => $nonConversionRate,
        ];
    }

    private function getFinancialPerformanceCardDrilldownUiOracle(array $filters, string $mode, string $rowOwnerDidarId = ''): array
    {
        $oracle = $this->getUiOracleKpiData($filters);
        $ids = $mode === 'settled'
            ? array_keys($oracle['sets']['financial_settled'] ?? [])
            : array_keys($oracle['sets']['financial_outstanding'] ?? []);

        $ids = array_map('intval', array_filter($ids, static function ($value) {
            return (int)$value > 0;
        }));
        if (empty($ids)) {
            return ['leads' => [], 'deals' => [], 'activities' => [], 'payments' => [], 'meta' => ['total_leads' => 0, 'total_deals' => 0, 'total_activities' => 0, 'total_payments' => 0]];
        }

        if ($rowOwnerDidarId !== '') {
            $pdo = $this->db->getPdo();
            $placeholders = implode(',', array_fill(0, count($ids), '?'));
            $stmt = $pdo->prepare("SELECT id FROM deals WHERE id IN ({$placeholders}) AND owner_didar_id = ?");
            $stmt->execute(array_merge($ids, [$rowOwnerDidarId]));
            $ids = array_map('intval', array_column($stmt->fetchAll(PDO::FETCH_ASSOC) ?: [], 'id'));
            if (empty($ids)) {
                return ['leads' => [], 'deals' => [], 'activities' => [], 'payments' => [], 'meta' => ['total_leads' => 0, 'total_deals' => 0, 'total_activities' => 0, 'total_payments' => 0]];
            }
        }

        $result = $this->buildSimpleDealLeadDrilldownByIds($ids, $filters);
        if ($mode === 'outstanding') {
            if (!isset($result['meta']) || !is_array($result['meta'])) {
                $result['meta'] = [];
            }
            $result['meta']['drilldown_kind'] = 'outstanding_balance';
            $result['meta']['row_type'] = 'deal';
            $result['meta']['title'] = 'مانده حساب معاملات';
        }
        return $result;
    }

    private function buildSimpleDealLeadDrilldownByIds(array $dealIds, array $filters, string $totalsBasis = 'payment'): array
    {
        $dealIds = array_values(array_unique(array_map('intval', $dealIds)));
        if (empty($dealIds)) {
            return ['leads' => [], 'deals' => [], 'activities' => [], 'payments' => [], 'meta' => ['total_leads' => 0, 'total_deals' => 0, 'total_activities' => 0, 'total_payments' => 0]];
        }
        $pdo = $this->db->getPdo();
        $placeholders = implode(',', array_fill(0, count($dealIds), '?'));
        $paymentsByDealSql = $this->buildKpiAllTimePaymentTotalsByDealSql($filters, $totalsBasis === 'sales' ? 'sales' : 'payment');

        $dealsSql = "
            SELECT
                d.id as deal_id,
                d.didar_deal_id,
                d.title,
                d.status,
                d.pipeline_stage,
                COALESCE(d.acquaintance_source, '') as acquaintance_source,
                COALESCE(d.acquaintance_detail, '') as acquaintance_detail,
                COALESCE(d.content_topic, '') as content_topic,
                COALESCE(d.created_by_name, '') as created_by_name,
                COALESCE(d.created_via, '') as created_via,
                DATE(COALESCE(d.register_time, d.created_at)) as deal_date,
                COALESCE(u.display_name, 'بدون مسئول') as owner_name,
                COALESCE(d.owner_didar_id, '') as owner_didar_id,
                COALESCE(d.contact_didar_id, '') as contact_didar_id,
                COALESCE(p.customer_code, '') as customer_code,
                COALESCE(p.acquaintance_source, '') as lead_acquaintance_source,
                COALESCE(p.acquaintance_detail, '') as lead_acquaintance_detail,
                COALESCE(p.sale_status, '') as lead_sale_status,
                DATE(COALESCE(p.register_time, p.created_at)) as lead_register_time,
                COALESCE(p.register_time_jalali, '') as lead_register_time_jalali,
                CASE
                    WHEN d.status = 'Won' THEN 'won'
                    WHEN d.status = 'Lost' THEN 'lost'
                    ELSE 'pending'
                END as deal_status_group,
                COALESCE(d.payable_amount, 0) as payable_amount,
                COALESCE(pay.total_paid, 0) as total_paid,
                CASE WHEN COALESCE(pay.total_paid, 0) > 0 THEN 1 ELSE 0 END as has_payment,
                {$this->buildFailureReasonExpr('d')} as failure_reason
            FROM deals d
            LEFT JOIN users u ON u.didar_user_id = d.owner_didar_id
            LEFT JOIN persons p ON p.didar_contact_id = d.contact_didar_id
            LEFT JOIN ({$paymentsByDealSql}) pay ON pay.deal_id = d.id
            WHERE d.id IN ({$placeholders})
            ORDER BY deal_date DESC, d.id DESC
        ";
        $deals = $this->runSelectAll($dealsSql, $dealIds);

        $contactIds = [];
        foreach ($deals as $deal) {
            $contactId = trim((string)($deal['contact_didar_id'] ?? ''));
            if ($contactId !== '') {
                $contactIds[$contactId] = true;
            }
        }
        $leads = [];
        if (!empty($contactIds)) {
            $contactList = array_keys($contactIds);
            $contactPlaceholders = implode(',', array_fill(0, count($contactList), '?'));
            $leadsSql = "
                SELECT
                    p.didar_contact_id as contact_id,
                    TRIM(CONCAT(COALESCE(p.first_name, ''), ' ', COALESCE(p.last_name, ''))) as full_name,
                    COALESCE(p.customer_code, '') as customer_code,
                    p.mobile_phone,
                    p.secondary_mobile_phone,
                    COALESCE(p.sale_status, '') as sale_status,
                    COALESCE(p.acquaintance_source, '') as acquaintance_source,
                    COALESCE(p.acquaintance_detail, '') as acquaintance_detail,
                    COALESCE(p.content_topic, '') as content_topic,
                    COALESCE(p.created_by_name, '') as created_by_name,
                    COALESCE(p.created_via, '') as created_via,
                    (SELECT COUNT(*) FROM deals dx WHERE dx.contact_didar_id = p.didar_contact_id) as deal_count,
                    (SELECT COUNT(*) FROM deals dx WHERE dx.contact_didar_id = p.didar_contact_id AND dx.status = 'Won') as won_deal_count,
                    (SELECT COUNT(*) FROM deals dx WHERE dx.contact_didar_id = p.didar_contact_id AND dx.status = 'Lost') as lost_deal_count,
                    (SELECT COUNT(*) FROM deals dx WHERE dx.contact_didar_id = p.didar_contact_id AND (dx.status IS NULL OR dx.status = '' OR dx.status = 'Pending')) as current_deal_count,
                    DATE(COALESCE(p.register_time, p.created_at)) as lead_date,
                    COALESCE(u.display_name, 'بدون مسئول') as owner_name
                FROM persons p
                LEFT JOIN users u ON u.didar_user_id = p.owner_didar_id
                WHERE p.didar_contact_id IN ({$contactPlaceholders})
                ORDER BY lead_date DESC, full_name ASC
            ";
            $leads = $this->runSelectAll($leadsSql, $contactList);
        }

        $payments = $this->getPaymentsByDealIds($dealIds);

        return [
            'leads' => $leads,
            'deals' => $deals,
            'activities' => [],
            'payments' => $payments,
            'meta' => [
                'total_leads' => count($leads),
                'total_deals' => count($deals),
                'total_activities' => 0,
                'total_payments' => count($payments)
            ]
        ];
    }

    private function getUiOracleKpiData(array $filters): array
    {
        $cacheKey = md5(json_encode([
            'date_from' => (string)($filters['date_from'] ?? ''),
            'date_to' => (string)($filters['date_to'] ?? ''),
            'owner_didar_id' => (string)($filters['owner_didar_id'] ?? ''),
            'owner_didar_ids_csv' => (string)($filters['owner_didar_ids_csv'] ?? ''),
            'product_id' => (string)($filters['product_id'] ?? ''),
            'product_ids_csv' => (string)($filters['product_ids_csv'] ?? ''),
            'deal_statuses_csv' => (string)($filters['deal_statuses_csv'] ?? ''),
            'deal_lead_types_csv' => (string)($filters['deal_lead_types_csv'] ?? ''),
            'legacy_deal_scope' => (string)($filters['legacy_deal_scope'] ?? ''),
            'kpi_sales_count_rule' => (string)($filters['kpi_sales_count_rule'] ?? ''),
            'kpi_payment_count_rule' => (string)($filters['kpi_payment_count_rule'] ?? ''),
            'kpi_payment_status_scope' => (string)($filters['kpi_payment_status_scope'] ?? ''),
        ], JSON_UNESCAPED_UNICODE));
        if (isset($this->uiOracleCache[$cacheKey])) {
            return $this->uiOracleCache[$cacheKey];
        }

        $state = $this->buildUiOracleState($filters);
        $cohortIds = $this->uiOracleDealCohortIds($state, $filters);
        $noDateIds = $this->uiOracleDealNoDateFilteredIds($state, $filters);

        $salesBasis = $this->getKpiPaymentDateBasis($filters, 'sales');
        $salesStatusScope = $this->getKpiPaymentStatusScope($filters);
        $salesEventsWindow = $this->uiOraclePaymentEvents($state, $salesBasis, $salesStatusScope, true, $filters, 'sales');
        $salesWindowAgg = $this->uiOracleAggregateByDeal($salesEventsWindow, $this->getKpiPaymentCountRule($filters, 'sales'));
        $salesFirstAllAgg = $this->uiOracleAggregateByDeal(
            $this->uiOraclePaymentEvents($state, $salesBasis, $salesStatusScope, false, $filters, 'sales'),
            'first_payment_only'
        );
        $salesAllAgg = $this->uiOracleAggregateByDeal(
            $this->uiOraclePaymentEvents($state, $salesBasis, $salesStatusScope, false, $filters, 'sales'),
            'all_payments'
        );

        $paymentEventsWindow = $this->uiOraclePaymentEvents($state, 'ui_payment_date', $this->getKpiPaymentStatusScope($filters), true, $filters, 'payment');
        $paymentEventsAll = $this->uiOraclePaymentEvents($state, 'ui_payment_date', $this->getKpiPaymentStatusScope($filters), false, $filters, 'payment');
        $paymentWindowAgg = $this->uiOracleAggregateByDeal(
            $paymentEventsWindow,
            $this->getKpiPaymentCountRule($filters, 'payment')
        );
        $paymentAllAgg = $this->uiOracleAggregateByDeal(
            $paymentEventsAll,
            'all_payments'
        );

        $salesCardQualified = [];
        $salesCardContract = 0.0;
        $salesCardCollected = 0.0;
        $rangeFrom = trim((string)($filters['date_from'] ?? ''));
        $rangeTo = trim((string)($filters['date_to'] ?? ''));
        foreach ($salesFirstAllAgg as $dealId => $firstAgg) {
            if (!isset($noDateIds[$dealId]) || !isset($state['deals'][$dealId])) {
                continue;
            }
            if ((string)($state['deals'][$dealId]['status'] ?? '') === 'Lost') {
                continue;
            }
            $firstDate = (string)($firstAgg['first'] ?? '');
            if ($firstDate === '') {
                continue;
            }
            if ($rangeFrom !== '' && $firstDate < $rangeFrom) {
                continue;
            }
            if ($rangeTo !== '' && $firstDate > $rangeTo) {
                continue;
            }
            $windowPaid = (float)($salesWindowAgg[$dealId]['total'] ?? 0);
            if ($windowPaid <= 0) {
                continue;
            }
            $salesCardQualified[$dealId] = true;
            $salesCardContract += (float)($state['deals'][$dealId]['payable_amount'] ?? 0);
            $salesCardCollected += (float)($salesWindowAgg[$dealId]['total'] ?? 0);
        }

        $financial = [
            'paid_deals_count' => 0,
            'paid_deals_contract_sum' => 0.0,
            'paid_deals_collected_sum' => 0.0,
            'outstanding_count' => 0,
            'outstanding_amount_sum' => 0.0,
            'outstanding_contract_sum' => 0.0,
            'outstanding_collected_sum' => 0.0,
            'settled_count' => 0,
            'settled_contract_sum' => 0.0,
            'settled_collected_sum' => 0.0,
        ];
        $financialSettled = [];
        $financialOutstanding = [];
        $rangeFrom = trim((string)($filters['date_from'] ?? ''));
        $rangeTo = trim((string)($filters['date_to'] ?? ''));
        foreach ($paymentWindowAgg as $dealId => $windowRow) {
            if (!isset($noDateIds[$dealId])) {
                continue;
            }
            if (!isset($state['deals'][$dealId])) {
                continue;
            }
            $deal = $state['deals'][$dealId];
            $firstSalesDate = (string)($salesFirstAllAgg[$dealId]['first'] ?? '');
            if ($firstSalesDate === '') {
                continue;
            }
            if ($rangeFrom !== '' && $firstSalesDate < $rangeFrom) {
                continue;
            }
            if ($rangeTo !== '' && $firstSalesDate > $rangeTo) {
                continue;
            }
            if ((string)($deal['status'] ?? '') === 'Lost') {
                continue;
            }
            $payable = (float)($deal['payable_amount'] ?? 0);
            $paidAll = (float)($paymentAllAgg[$dealId]['total'] ?? 0);

            $financial['paid_deals_count']++;
            $financial['paid_deals_contract_sum'] += $payable;
            $financial['paid_deals_collected_sum'] += $paidAll;

            $isSettled = (($payable <= 0 && $paidAll > 0) || ($payable > 0 && $paidAll >= $payable));
            $isOutstanding = ($payable > 0 && $paidAll > 0 && $paidAll < $payable);

            if ($isSettled) {
                $financial['settled_count']++;
                $financial['settled_contract_sum'] += $payable;
                $financial['settled_collected_sum'] += $paidAll;
                $financialSettled[$dealId] = true;
            } elseif ($isOutstanding) {
                $financial['outstanding_count']++;
                $financial['outstanding_amount_sum'] += ($payable - $paidAll);
                $financial['outstanding_contract_sum'] += $payable;
                $financial['outstanding_collected_sum'] += $paidAll;
                $financialOutstanding[$dealId] = true;
            }
        }

        $convCohort = [];
        $convConverted = [];
        $partitionLost = [];
        $partitionOpen = [];
        $partitionWon = [];
        $partitionWithoutPayment = [];
        $partitionWithPaymentNonLost = [];
        $partitionLostAmount = 0.0;
        $partitionOpenAmount = 0.0;
        $partitionWonAmount = 0.0;
        $partitionWithoutPaymentAmount = 0.0;
        $registeredInRangeCohort = [];
        $registeredInRangeAmount = 0.0;
        foreach ($cohortIds as $dealId => $_) {
            if (!isset($state['deals'][$dealId])) {
                continue;
            }
            $dealDate = (string)($state['deals'][$dealId]['deal_date'] ?? '');
            $first = (string)($salesFirstAllAgg[$dealId]['first'] ?? '');
            $convCohort[$dealId] = true;
            $registeredInRangeCohort[$dealId] = true;
            if ((float)($salesWindowAgg[$dealId]['total'] ?? 0) > 0) {
                $convConverted[$dealId] = true;
            }

            $deal = $state['deals'][$dealId];
            $status = (string)($deal['status'] ?? '');
            $payable = (float)($deal['payable_amount'] ?? 0);
            $registeredInRangeAmount += $payable;
            $paidAllForSalesBasis = (float)($salesAllAgg[$dealId]['total'] ?? 0);
            $isSalesQualified = isset($salesCardQualified[$dealId]);
            $isFullyPaidWonSale = $paidAllForSalesBasis > 0 && ($payable <= 0 || $paidAllForSalesBasis >= $payable);
            if ($status === 'Lost') {
                $partitionLost[$dealId] = true;
                $partitionLostAmount += $payable;
                continue;
            }
            if ($status !== 'Lost' && $isSalesQualified && $isFullyPaidWonSale) {
                $partitionWon[$dealId] = true;
                $partitionWonAmount += $payable;
            } elseif ($status !== 'Lost' && $paidAllForSalesBasis <= 0) {
                $partitionOpen[$dealId] = true;
                $partitionOpenAmount += $payable;
            }
            if ($paidAllForSalesBasis > 0) {
                $partitionWithPaymentNonLost[$dealId] = true;
            } else {
                $partitionWithoutPayment[$dealId] = true;
                $partitionWithoutPaymentAmount += $payable;
            }
        }

        // Option A extension:
        // اگر معامله براساس «فروش کل» (اولین واریزی در بازه) حساب شده باشد اما deal_date آن خارج از بازه باشد،
        // باید در مرجع نرخ تبدیل و bucketهای status (pending/won/lost) هم وارد شود.
        $crossDateSalesAdded = [];
        $crossDateSalesAmount = 0.0;
        foreach ($salesCardQualified as $dealId => $_) {
            if (!isset($state['deals'][$dealId])) {
                continue;
            }
            if (!isset($noDateIds[$dealId])) {
                continue;
            }
            if (isset($convCohort[$dealId])) {
                continue;
            }
            $deal = $state['deals'][$dealId];
            $status = (string)($deal['status'] ?? '');
            $payable = (float)($deal['payable_amount'] ?? 0);
            $paidAllForSalesBasis = (float)($salesAllAgg[$dealId]['total'] ?? 0);
            $isFullyPaidWonSale = $paidAllForSalesBasis > 0 && ($payable <= 0 || $paidAllForSalesBasis >= $payable);
            $crossDateSalesAdded[$dealId] = true;
            $crossDateSalesAmount += $payable;
            $convCohort[$dealId] = true;
            $convConverted[$dealId] = true;
            if ($status === 'Lost') {
                $partitionLost[$dealId] = true;
                $partitionLostAmount += $payable;
            } elseif ($status !== 'Lost' && $isFullyPaidWonSale) {
                $partitionWon[$dealId] = true;
                $partitionWonAmount += $payable;
            } elseif ($status !== 'Lost' && $paidAllForSalesBasis <= 0) {
                $partitionOpen[$dealId] = true;
                $partitionOpenAmount += $payable;
            }
            $partitionWithPaymentNonLost[$dealId] = true;
        }

        // مرجع نرخ تبدیل: تمام معاملات ثبت‌شده در بازه + معاملات خارج بازه با اولین واریزی فروش در بازه.
        $convReferenceIds = $convCohort;

        $depositRelatedSplit = [
            'total' => ['deal_ids' => [], 'amount' => 0.0, 'payment_count' => 0],
            'registered_in_range' => ['deal_ids' => [], 'amount' => 0.0, 'payment_count' => 0],
            'outside_registered_first_payment' => ['deal_ids' => [], 'amount' => 0.0, 'payment_count' => 0],
        ];
        $depositCategorySplit = [
            'sales_deposit' => ['deal_ids' => [], 'amount' => 0.0, 'payment_count' => 0],
            'balance_deposit' => ['deal_ids' => [], 'amount' => 0.0, 'payment_count' => 0],
            'cheque_collected' => ['deal_ids' => [], 'amount' => 0.0, 'payment_count' => 0],
            'lost_deposit' => ['deal_ids' => [], 'amount' => 0.0, 'payment_count' => 0],
        ];
        foreach ($paymentEventsAll as $event) {
            $dealId = (string)($event['deal_id'] ?? '');
            if ($dealId === '' || !isset($convReferenceIds[$dealId])) {
                continue;
            }
            $amount = (float)($event['amount'] ?? 0);
            if ($amount <= 0) {
                continue;
            }
            $depositRelatedSplit['total']['amount'] += $amount;
            $depositRelatedSplit['total']['payment_count']++;
            $depositRelatedSplit['total']['deal_ids'][$dealId] = true;

            if (isset($registeredInRangeCohort[$dealId])) {
                $depositRelatedSplit['registered_in_range']['amount'] += $amount;
                $depositRelatedSplit['registered_in_range']['payment_count']++;
                $depositRelatedSplit['registered_in_range']['deal_ids'][$dealId] = true;
            } elseif (isset($crossDateSalesAdded[$dealId])) {
                $depositRelatedSplit['outside_registered_first_payment']['amount'] += $amount;
                $depositRelatedSplit['outside_registered_first_payment']['payment_count']++;
                $depositRelatedSplit['outside_registered_first_payment']['deal_ids'][$dealId] = true;
            }

            $dealStatus = (string)($state['deals'][$dealId]['status'] ?? '');
            if ($dealStatus === 'Lost') {
                $categoryKey = 'lost_deposit';
            } else {
                $categoryKey = !empty($event['is_cheque'])
                    ? 'cheque_collected'
                    : (((string)($event['date'] ?? '') === (string)($salesFirstAllAgg[$dealId]['first'] ?? '')) ? 'sales_deposit' : 'balance_deposit');
            }
            $depositCategorySplit[$categoryKey]['amount'] += $amount;
            $depositCategorySplit[$categoryKey]['payment_count']++;
            $depositCategorySplit[$categoryKey]['deal_ids'][$dealId] = true;
        }
        $depositContractAmount = static function (array $dealIds) use ($state): float {
            $sum = 0.0;
            foreach ($dealIds as $dealId => $_) {
                $sum += (float)($state['deals'][(string)$dealId]['payable_amount'] ?? 0);
            }
            return $sum;
        };
        $salesWonOverlap = [];
        foreach ($salesCardQualified as $dealId => $_) {
            if (isset($partitionWon[$dealId])) {
                $salesWonOverlap[$dealId] = true;
            }
        }

        $result = [
            'state' => $state,
            'aggs' => [
                'sales_window' => $salesWindowAgg,
                'sales_first_all' => $salesFirstAllAgg,
                'sales_all' => $salesAllAgg,
                'payment_window' => $paymentWindowAgg,
                'payment_all' => $paymentAllAgg,
            ],
            'sales_card' => [
                'deal_count' => count($salesCardQualified),
                'total_contract_amount' => $salesCardContract,
                'total_collected_amount' => $salesCardCollected,
            ],
            'financial_window' => $financial,
            'deal_conversion' => [
                'total_deals' => count($convReferenceIds),
                'converted_deals' => count($salesCardQualified),
            ],
            'deal_related_split' => [
                'total_count' => count($convReferenceIds),
                'total_amount' => $registeredInRangeAmount + $crossDateSalesAmount,
                'registered_in_range_count' => count($registeredInRangeCohort),
                'registered_in_range_amount' => $registeredInRangeAmount,
                'outside_registered_first_payment_count' => count($crossDateSalesAdded),
                'outside_registered_first_payment_amount' => $crossDateSalesAmount,
            ],
            'deposit_related_split' => [
                'total_amount' => (float)$depositRelatedSplit['total']['amount'],
                'total_contract_amount' => $depositContractAmount($depositRelatedSplit['total']['deal_ids']),
                'total_deal_count' => count($depositRelatedSplit['total']['deal_ids']),
                'total_payment_count' => (int)$depositRelatedSplit['total']['payment_count'],
                'registered_in_range_amount' => (float)$depositRelatedSplit['registered_in_range']['amount'],
                'registered_in_range_contract_amount' => $depositContractAmount($depositRelatedSplit['registered_in_range']['deal_ids']),
                'registered_in_range_deal_count' => count($depositRelatedSplit['registered_in_range']['deal_ids']),
                'registered_in_range_payment_count' => (int)$depositRelatedSplit['registered_in_range']['payment_count'],
                'outside_registered_first_payment_amount' => (float)$depositRelatedSplit['outside_registered_first_payment']['amount'],
                'outside_registered_first_payment_contract_amount' => $depositContractAmount($depositRelatedSplit['outside_registered_first_payment']['deal_ids']),
                'outside_registered_first_payment_deal_count' => count($depositRelatedSplit['outside_registered_first_payment']['deal_ids']),
                'outside_registered_first_payment_payment_count' => (int)$depositRelatedSplit['outside_registered_first_payment']['payment_count'],
            ],
            'deposit_category_split' => [
                'sales_deposit' => [
                    'amount' => (float)$depositCategorySplit['sales_deposit']['amount'],
                    'deal_count' => count($depositCategorySplit['sales_deposit']['deal_ids']),
                    'payment_count' => (int)$depositCategorySplit['sales_deposit']['payment_count'],
                ],
                'balance_deposit' => [
                    'amount' => (float)$depositCategorySplit['balance_deposit']['amount'],
                    'deal_count' => count($depositCategorySplit['balance_deposit']['deal_ids']),
                    'payment_count' => (int)$depositCategorySplit['balance_deposit']['payment_count'],
                ],
                'cheque_collected' => [
                    'amount' => (float)$depositCategorySplit['cheque_collected']['amount'],
                    'deal_count' => count($depositCategorySplit['cheque_collected']['deal_ids']),
                    'payment_count' => (int)$depositCategorySplit['cheque_collected']['payment_count'],
                ],
                'lost_deposit' => [
                    'amount' => (float)$depositCategorySplit['lost_deposit']['amount'],
                    'deal_count' => count($depositCategorySplit['lost_deposit']['deal_ids']),
                    'payment_count' => (int)$depositCategorySplit['lost_deposit']['payment_count'],
                ],
            ],
            'conversion_partition' => [
                'lost_count' => count($partitionLost),
                'lost_amount' => $partitionLostAmount,
                'open_valid_count' => count($partitionOpen),
                'open_valid_amount' => $partitionOpenAmount,
                'won_count' => count($partitionWon),
                'won_amount' => $partitionWonAmount,
                'without_payment_count' => count($partitionWithoutPayment),
                'without_payment_amount' => $partitionWithoutPaymentAmount,
                'with_payment_non_lost_count' => count($partitionWithPaymentNonLost),
            ],
            'sets' => [
                'financial_settled' => $financialSettled,
                'financial_outstanding' => $financialOutstanding,
                'sales_card_qualified' => $salesCardQualified,
                'conversion_cohort' => $convCohort,
                'conversion_registered_in_range' => $registeredInRangeCohort,
                'conversion_converted' => $convConverted,
                'conversion_reference' => $convReferenceIds,
                'conversion_cross_date_sales_added' => $crossDateSalesAdded,
                'deposit_related_total' => $depositRelatedSplit['total']['deal_ids'],
                'deposit_registered_in_range' => $depositRelatedSplit['registered_in_range']['deal_ids'],
                'deposit_outside_registered_first_payment' => $depositRelatedSplit['outside_registered_first_payment']['deal_ids'],
                'conversion_sales_won_overlap' => $salesWonOverlap,
                'partition_lost_valid' => $partitionLost,
                'partition_open_valid' => $partitionOpen,
                'partition_won_valid' => $partitionWon,
                'partition_without_payment_valid' => $partitionWithoutPayment,
                'partition_with_payment_non_lost' => $partitionWithPaymentNonLost,
            ],
        ];
        $this->uiOracleCache[$cacheKey] = $result;
        return $result;
    }

    private function buildUiOracleState(array $filters): array
    {
        $whereDealsNoDate = $this->buildWhereClauseForDealsNoDate($filters, 'd');
        $paramsDealsNoDate = $this->buildParamsForDealsNoDate($filters);
        $dealsRows = $this->runSelectAll(
            "SELECT d.*, DATE(COALESCE(d.register_time, d.created_at)) as deal_date FROM deals d WHERE {$whereDealsNoDate}",
            $paramsDealsNoDate
        );
        $deals = [];
        foreach ($dealsRows as $row) {
            $deals[(string)$row['id']] = $row;
        }

        if (empty($deals)) {
            return [
                'deals' => [],
                'personsByContact' => [],
                'txByDeal' => [],
                'paymentRecordsByDeal' => [],
                'dealProducts' => [],
                'dealProductSnapshots' => [],
                'jsonByDeal' => [],
                'filters' => $filters,
            ];
        }

        $dealIds = array_keys($deals);
        $dealPlaceholders = implode(',', array_fill(0, count($dealIds), '?'));

        $contactIds = [];
        foreach ($deals as $deal) {
            $contactId = trim((string)($deal['contact_didar_id'] ?? ''));
            if ($contactId !== '') {
                $contactIds[$contactId] = true;
            }
        }
        $personsRows = [];
        if (!empty($contactIds)) {
            $contactList = array_keys($contactIds);
            $contactPlaceholders = implode(',', array_fill(0, count($contactList), '?'));
            $personsRows = $this->runSelectAll(
                "SELECT didar_contact_id, DATE(COALESCE(register_time, created_at)) as pdate FROM persons WHERE didar_contact_id IN ({$contactPlaceholders})",
                $contactList
            );
        }
        $personsByContact = [];
        foreach ($personsRows as $row) {
            $personsByContact[(string)$row['didar_contact_id']] = (string)($row['pdate'] ?? '');
        }

        $txMethodSelect = $this->db->hasColumn('transactions', 'method') ? 'method' : "'' AS method";
        $txRows = $this->runSelectAll(
            "SELECT deal_id, amount, payment_date, created_at, status, {$txMethodSelect} FROM transactions WHERE COALESCE(amount,0) > 0 AND deal_id IN ({$dealPlaceholders})",
            $dealIds
        );
        $txByDeal = [];
        foreach ($txRows as $row) {
            $txByDeal[(string)$row['deal_id']][] = $row;
        }

        $paymentRecordsByDeal = [];
        if ($this->tableHasColumns('payment_records', ['id', 'deal_id', 'amount'])) {
            $paymentDate = $this->ceoPaymentDateExpr('pr');
            $netAmount = $this->ceoPaymentNetAmountExpr('pr');
            $isCheque = $this->ceoPaymentIsChequeExpr('pr');
            $isSudran = $this->ceoPaymentIsSudranExpr('pr');
            $valid = $this->ceoPaymentValidWhere('pr');
            $paymentStatusSelect = $this->db->hasColumn('payment_records', 'status')
                ? "COALESCE(pr.status, 'confirmed') as status"
                : "'confirmed' as status";
            $paymentRows = $this->runSelectAll("
                SELECT
                    pr.deal_id,
                    {$netAmount} as amount,
                    {$paymentDate} as payment_date,
                    pr.created_at,
                    {$paymentStatusSelect},
                    COALESCE(pr.method, '') as method,
                    CASE WHEN {$isCheque} THEN 1 ELSE 0 END as is_cheque,
                    CASE WHEN {$isSudran} THEN 1 ELSE 0 END as is_sudran,
                    COALESCE(pr.cheque_collection_status, 'not_cheque') as cheque_collection_status
                FROM payment_records pr
                WHERE pr.deal_id IN ({$dealPlaceholders})
                  AND {$valid}
                  AND {$netAmount} > 0
                  AND {$paymentDate} IS NOT NULL
            ", $dealIds);
            foreach ($paymentRows as $row) {
                $paymentRecordsByDeal[(string)$row['deal_id']][] = $row;
            }
        }

        $snapshotSelects = [];
        $snapshotSelects[] = $this->db->hasColumn('deal_products', 'product_name_snapshot')
            ? 'product_name_snapshot'
            : 'NULL AS product_name_snapshot';
        $snapshotSelects[] = $this->db->hasColumn('deal_products', 'service_title_snapshot')
            ? 'service_title_snapshot'
            : 'NULL AS service_title_snapshot';
        $dealProductsRows = $this->runSelectAll(
            "SELECT deal_id, product_id, " . implode(', ', $snapshotSelects) . " FROM deal_products WHERE deal_id IN ({$dealPlaceholders})",
            $dealIds
        );
        $dealProducts = [];
        $dealProductSnapshots = [];
        foreach ($dealProductsRows as $row) {
            $dealProducts[(string)$row['deal_id']][(string)$row['product_id']] = true;
            $dealProductSnapshots[(string)$row['deal_id']][(string)$row['product_id']] = [
                'product_name_snapshot' => (string)($row['product_name_snapshot'] ?? ''),
                'service_title_snapshot' => (string)($row['service_title_snapshot'] ?? ''),
            ];
        }

        $jsonByDeal = [];
        foreach ($deals as $dealId => $deal) {
            $jsonByDeal[$dealId] = $this->uiOracleJsonPayments((string)($deal['payments'] ?? ''));
        }

        return [
            'deals' => $deals,
            'personsByContact' => $personsByContact,
            'txByDeal' => $txByDeal,
            'paymentRecordsByDeal' => $paymentRecordsByDeal,
            'dealProducts' => $dealProducts,
            'dealProductSnapshots' => $dealProductSnapshots,
            'jsonByDeal' => $jsonByDeal,
            'filters' => $filters,
        ];
    }

    private function uiOracleDealCohortIds(array $state, array $filters): array
    {
        $result = [];
        foreach ($state['deals'] as $dealId => $deal) {
            if ($this->uiOracleDealPass($deal, $state, $filters)) {
                $result[$dealId] = true;
            }
        }
        return $result;
    }

    private function uiOracleDealNoDateFilteredIds(array $state, array $filters): array
    {
        $result = [];
        foreach ($state['deals'] as $dealId => $deal) {
            if ($this->uiOracleDealPassNoDate($deal, $state, $filters)) {
                $result[$dealId] = true;
            }
        }
        return $result;
    }

    private function uiOracleDealPass(array $deal, array $state, array $filters): bool
    {
        $dealDate = (string)($deal['deal_date'] ?? '');
        if (!$this->uiOracleInRange($dealDate, $filters)) {
            return false;
        }
        return $this->uiOracleDealPassNoDate($deal, $state, $filters);
    }

    private function uiOracleDealPassNoDate(array $deal, array $state, array $filters): bool
    {
        $legacy = (int)($deal['is_legacy_didar'] ?? 0) === 1;
        $legacyScope = strtolower(trim((string)($filters['legacy_deal_scope'] ?? 'exclude')));
        if ($legacyScope === 'exclude' && $legacy) {
            return false;
        }
        if ($legacyScope === 'only' && !$legacy) {
            return false;
        }

        $ownerId = trim((string)($deal['owner_didar_id'] ?? ''));
        $ownerCsv = trim((string)($filters['owner_didar_ids_csv'] ?? ''));
        $ownerSingle = trim((string)($filters['owner_didar_id'] ?? ''));
        if ($ownerCsv !== '') {
            $owners = array_filter(array_map('trim', explode(',', $ownerCsv)), static function ($v) { return $v !== ''; });
            if (!in_array($ownerId, $owners, true)) {
                return false;
            }
        } elseif ($ownerSingle !== '' && $ownerId !== $ownerSingle) {
            return false;
        }

        $productCsv = trim((string)($filters['product_ids_csv'] ?? ''));
        $productSingle = trim((string)($filters['product_id'] ?? ''));
        if ($productCsv !== '' || $productSingle !== '') {
            $dealProductSet = $state['dealProducts'][(string)$deal['id']] ?? [];
            if ($productCsv !== '') {
                $allowed = array_filter(array_map('trim', explode(',', $productCsv)), static function ($v) { return $v !== ''; });
                $matched = false;
                foreach ($allowed as $pid) {
                    if (isset($dealProductSet[$pid])) {
                        $matched = true;
                        break;
                    }
                }
                if (!$matched) {
                    return false;
                }
            } elseif (!isset($dealProductSet[$productSingle])) {
                return false;
            }
        }

        $statusCsv = trim((string)($filters['deal_statuses_csv'] ?? ''));
        if ($statusCsv !== '') {
            $statusGroup = ((string)($deal['status'] ?? '') === 'Won')
                ? 'won'
                : (((string)($deal['status'] ?? '') === 'Lost') ? 'lost' : 'pending');
            $allowed = array_filter(array_map('trim', explode(',', $statusCsv)), static function ($v) { return $v !== ''; });
            if (!in_array($statusGroup, $allowed, true)) {
                return false;
            }
        }

        $leadTypeCsv = trim((string)($filters['deal_lead_types_csv'] ?? ''));
        if ($leadTypeCsv !== '') {
            $leadType = $this->uiOracleLeadType($deal, $state);
            $allowed = array_filter(array_map('trim', explode(',', $leadTypeCsv)), static function ($v) { return $v !== ''; });
            if (!in_array($leadType, $allowed, true)) {
                return false;
            }
        }

        return true;
    }

    private function resolveActivityPerformanceDealDate($dealId, array $deal, array $salesFirstAllAgg): string
    {
        $dealDate = trim((string)($deal['deal_date'] ?? ''));
        $firstPaymentDate = trim((string)($salesFirstAllAgg[(string)$dealId]['first'] ?? ''));
        if ($dealDate !== '' && $firstPaymentDate !== '' && $firstPaymentDate < $dealDate) {
            return $firstPaymentDate;
        }
        return $dealDate;
    }

    private function uiOracleLeadType(array $deal, array $state): string
    {
        $contactId = (string)($deal['contact_didar_id'] ?? '');
        $leadDate = (string)($state['personsByContact'][$contactId] ?? '');
        $dealDate = (string)($deal['deal_date'] ?? '');
        return ($leadDate !== '' && $leadDate === $dealDate) ? 'new' : 'old';
    }

    private function uiOraclePaymentEvents(array $state, string $basis, string $statusScope, bool $window, array $filters, string $scope = 'payment'): array
    {
        $events = [];
        foreach ($state['deals'] as $dealId => $deal) {
            if ($basis === 'ui_payment_date') {
                $ledgerRows = $state['paymentRecordsByDeal'][$dealId] ?? [];
                if (!empty($ledgerRows)) {
                    foreach ($ledgerRows as $payment) {
                        if (!$this->uiOraclePaymentRecordPass($payment, $scope)) {
                            continue;
                        }
                        $amount = (float)($payment['amount'] ?? 0);
                        $date = $this->uiOracleNormalizeDate((string)($payment['payment_date'] ?? ''));
                        if ($amount <= 0 || $date === '') {
                            continue;
                        }
                        if ($window && !$this->uiOracleInRange($date, $filters)) {
                            continue;
                        }
                        $events[] = [
                            'deal_id' => (string)$dealId,
                            'date' => $date,
                            'amount' => $amount,
                            'is_cheque' => (int)($payment['is_cheque'] ?? 0) === 1,
                        ];
                    }
                    continue;
                }

                $jsonPayments = $state['jsonByDeal'][$dealId] ?? [];
                if (!empty($jsonPayments)) {
                    foreach ($jsonPayments as $payment) {
                        if (!$this->uiOracleJsonPaymentPass($payment, $scope)) {
                            continue;
                        }
                        $amount = (float)($payment['amount'] ?? 0);
                        $date = (string)($payment['date'] ?? '');
                        if ($amount <= 0 || $date === '') {
                            continue;
                        }
                        if ($window && !$this->uiOracleInRange($date, $filters)) {
                            continue;
                        }
                        $method = (string)(($payment['method'] ?? '') ?: ($payment['payment_method'] ?? ''));
                        $events[] = [
                            'deal_id' => (string)$dealId,
                            'date' => $date,
                            'amount' => $amount,
                            'is_cheque' => $this->uiOraclePaymentMethodIsCheque($method)
                                || trim((string)($payment['check_number'] ?? '')) !== ''
                                || trim((string)($payment['check_due_date'] ?? '')) !== ''
                                || trim((string)($payment['check_date'] ?? '')) !== '',
                        ];
                    }
                    continue;
                }
                foreach (($state['txByDeal'][$dealId] ?? []) as $tx) {
                    if (!$this->uiOracleTxPass($tx, $statusScope)) {
                        continue;
                    }
                    if ($this->uiOraclePaymentMethodIsSudran((string)($tx['method'] ?? ''))) {
                        continue;
                    }
                    $date = $this->uiOracleNormalizeDate((string)($tx['payment_date'] ?? ''));
                    if ($date === '') {
                        continue;
                    }
                    if ($window && !$this->uiOracleInRange($date, $filters)) {
                        continue;
                    }
                    $events[] = [
                        'deal_id' => (string)$dealId,
                        'date' => $date,
                        'amount' => (float)($tx['amount'] ?? 0),
                        'is_cheque' => $this->uiOraclePaymentMethodIsCheque((string)($tx['method'] ?? '')),
                    ];
                }
                continue;
            }

            foreach (($state['txByDeal'][$dealId] ?? []) as $tx) {
                if (!$this->uiOracleTxPass($tx, $statusScope)) {
                    continue;
                }
                if ($this->uiOraclePaymentMethodIsSudran((string)($tx['method'] ?? ''))) {
                    continue;
                }
                $date = $basis === 'system_created_at'
                    ? substr((string)($tx['created_at'] ?? ''), 0, 10)
                    : $this->uiOracleNormalizeDate((string)($tx['payment_date'] ?? ''));
                if ($date === '') {
                    continue;
                }
                if ($window && !$this->uiOracleInRange($date, $filters)) {
                    continue;
                }
                $events[] = [
                    'deal_id' => (string)$dealId,
                    'date' => $date,
                    'amount' => (float)($tx['amount'] ?? 0),
                    'is_cheque' => $this->uiOraclePaymentMethodIsCheque((string)($tx['method'] ?? '')),
                ];
            }
        }
        return $events;
    }

    private function uiOraclePaymentRecordPass(array $payment, string $scope): bool
    {
        if ((int)($payment['is_sudran'] ?? 0) === 1) {
            return false;
        }
        $isCheque = (int)($payment['is_cheque'] ?? 0) === 1;
        if ($scope === 'sales') {
            return !$isCheque;
        }
        return !$isCheque || (string)($payment['cheque_collection_status'] ?? '') === 'collected';
    }

    private function uiOracleJsonPaymentPass(array $payment, string $scope): bool
    {
        if (!empty($payment['deleted']) || !empty($payment['is_deleted'])) {
            return false;
        }
        $refundStatus = strtolower(trim((string)($payment['refund_status'] ?? '')));
        if ($refundStatus === 'full') {
            return false;
        }
        $method = (string)(($payment['method'] ?? '') ?: ($payment['payment_method'] ?? ''));
        $description = (string)($payment['description'] ?? '');
        if ($this->uiOraclePaymentMethodIsSudran($method . ' ' . $description)) {
            return false;
        }
        $isCheque = $this->uiOraclePaymentMethodIsCheque($method)
            || trim((string)($payment['check_number'] ?? '')) !== ''
            || trim((string)($payment['check_due_date'] ?? '')) !== ''
            || trim((string)($payment['check_date'] ?? '')) !== '';
        if ($scope === 'sales') {
            return !$isCheque;
        }
        if (!$isCheque) {
            return true;
        }
        $collectionStatus = strtolower(trim((string)($payment['cheque_collection_status'] ?? $payment['check_collection_status'] ?? '')));
        return in_array($collectionStatus, ['collected', 'cashed', 'passed', 'وصول شده', 'پاس شده'], true);
    }

    private function uiOraclePaymentMethodIsSudran(string $value): bool
    {
        $code = $this->normalizeCeoPaymentMethodCode($value);
        return in_array($code, ['sudran_payment_gateway', 'sudran_gateway', 'soodran_payment_gateway', 'soodran_gateway', 'sodran_payment_gateway'], true)
            || strpos($value, 'سودران') !== false
            || strpos(strtolower($value), 'sudran') !== false
            || strpos(strtolower($value), 'soodran') !== false
            || strpos(strtolower($value), 'sodran') !== false;
    }

    private function uiOraclePaymentMethodIsCheque(string $value): bool
    {
        $code = $this->normalizeCeoPaymentMethodCode($value);
        return in_array($code, ['check', 'cheque'], true) || strpos($value, 'چک') !== false;
    }

    private function uiOracleAggregateByDeal(array $events, string $countRule): array
    {
        $rowsByDealDate = [];
        foreach ($events as $event) {
            $key = $event['deal_id'] . '|' . $event['date'];
            if (!isset($rowsByDealDate[$key])) {
                $rowsByDealDate[$key] = ['deal_id' => $event['deal_id'], 'date' => $event['date'], 'amount' => 0.0];
            }
            $rowsByDealDate[$key]['amount'] += (float)$event['amount'];
        }

        $grouped = [];
        foreach ($rowsByDealDate as $row) {
            $grouped[$row['deal_id']][] = $row;
        }

        $aggregated = [];
        foreach ($grouped as $dealId => $rows) {
            usort($rows, static function ($a, $b) {
                return strcmp((string)$a['date'], (string)$b['date']);
            });
            $firstDate = (string)$rows[0]['date'];
            $sum = 0.0;
            foreach ($rows as $row) {
                if ($countRule === 'first_payment_only' && (string)$row['date'] !== $firstDate) {
                    continue;
                }
                $sum += (float)$row['amount'];
            }
            $aggregated[(string)$dealId] = ['total' => $sum, 'first' => $firstDate];
        }

        return $aggregated;
    }

    private function uiOracleJsonPayments(string $rawPayments): array
    {
        $rawPayments = trim($rawPayments);
        if ($rawPayments === '') {
            return [];
        }
        $decoded = json_decode($rawPayments, true);
        if (!is_array($decoded)) {
            return [];
        }
        $result = [];
        foreach ($decoded as $item) {
            if (!is_array($item)) {
                continue;
            }
            $amount = (float)($item['amount'] ?? 0);
            if ($amount <= 0) {
                continue;
            }
            $date = $this->uiOracleNormalizeDate((string)($item['date'] ?? ''));
            if ($date === '') {
                $date = $this->uiOracleNormalizeDate((string)($item['payment_date'] ?? ''));
            }
            if ($date === '') {
                $date = $this->uiOracleJalaliToGregorianString((string)($item['date_persian'] ?? ''));
            }
            if ($date === '') {
                continue;
            }
            $result[] = [
                'date' => $date,
                'amount' => $amount,
                'method' => (string)(($item['method'] ?? '') ?: ($item['payment_method'] ?? '')),
                'description' => (string)($item['description'] ?? ''),
                'check_number' => (string)($item['check_number'] ?? ''),
                'check_date' => (string)($item['check_date'] ?? ''),
                'check_due_date' => (string)(($item['check_due_date'] ?? '') ?: ($item['cheque_due_date'] ?? '')),
                'cheque_collection_status' => (string)(($item['cheque_collection_status'] ?? '') ?: ($item['check_collection_status'] ?? '')),
                'refund_status' => (string)($item['refund_status'] ?? ''),
                'deleted' => !empty($item['deleted']) || !empty($item['is_deleted']),
            ];
        }
        return $result;
    }

    private function uiOracleNormalizeDate(string $value): string
    {
        return DateNormalizer::normalizeGregorianOrJalaliDate($value) ?: '';
    }

    private function uiOracleInRange(string $date, array $filters): bool
    {
        $from = trim((string)($filters['date_from'] ?? ''));
        $to = trim((string)($filters['date_to'] ?? ''));
        if ($date === '') {
            return false;
        }
        if ($from !== '' && $date < $from) {
            return false;
        }
        if ($to !== '' && $date > $to) {
            return false;
        }
        return true;
    }

    private function uiOracleTxPass(array $tx, string $statusScope): bool
    {
        if ($statusScope === 'all_statuses') {
            return true;
        }
        return strtolower(trim((string)($tx['status'] ?? ''))) === 'confirmed';
    }

    private function uiOracleJalaliToGregorianString(string $jalali): string
    {
        $jalali = trim(str_replace(['-', '.'], '/', $jalali));
        $jalali = strtr($jalali, [
            '۰' => '0', '۱' => '1', '۲' => '2', '۳' => '3', '۴' => '4',
            '۵' => '5', '۶' => '6', '۷' => '7', '۸' => '8', '۹' => '9',
            '٠' => '0', '١' => '1', '٢' => '2', '٣' => '3', '٤' => '4',
            '٥' => '5', '٦' => '6', '٧' => '7', '٨' => '8', '٩' => '9',
        ]);
        if (!preg_match('/^(\\d{4})\\/(\\d{1,2})\\/(\\d{1,2})$/', $jalali, $m)) {
            return '';
        }
        [$gy, $gm, $gd] = $this->uiOracleJalaliToGregorian((int)$m[1], (int)$m[2], (int)$m[3]);
        if ($gy < 1900 || $gy > 2100 || $gm < 1 || $gm > 12 || $gd < 1 || $gd > 31) {
            return '';
        }
        return sprintf('%04d-%02d-%02d', $gy, $gm, $gd);
    }

    private function uiOracleJalaliToGregorian(int $jy, int $jm, int $jd): array
    {
        $jy += 1595;
        $days = -355668 + (365 * $jy) + intdiv($jy, 33) * 8 + intdiv((($jy % 33) + 3), 4) + $jd + ($jm < 7 ? (($jm - 1) * 31) : ((($jm - 7) * 30) + 186));
        $gy = 400 * intdiv($days, 146097);
        $days %= 146097;
        if ($days > 36524) {
            $gy += 100 * intdiv(--$days, 36524);
            $days %= 36524;
            if ($days >= 365) {
                $days++;
            }
        }
        $gy += 4 * intdiv($days, 1461);
        $days %= 1461;
        if ($days > 365) {
            $gy += intdiv($days - 1, 365);
            $days = ($days - 1) % 365;
        }
        $gd = $days + 1;
        $sal = [0, 31, (($gy % 4 === 0 && $gy % 100 !== 0) || ($gy % 400 === 0)) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
        $gm = 1;
        while ($gm <= 12 && $gd > $sal[$gm]) {
            $gd -= $sal[$gm];
            $gm++;
        }
        return [$gy, $gm, $gd];
    }

    /**
     * هم‌راستاسازی جداول KPI پیشرفته با منطق مرجع UI Oracle برای KPIهای فروش/مالی/تبدیل.
     * فقط جدول‌های هم‌پوشان با کارت‌ها/نمودارها override می‌شوند؛ بقیه به SQL فعلی واگذار می‌شوند.
     */
    private function applyUiOracleAdvancedKpiOverrides(array $filters, array $tables): array
    {
        $oracle = $this->getUiOracleKpiData($filters);
        $state = $oracle['state'] ?? [];
        $deals = $state['deals'] ?? [];
        if (empty($deals)) {
            return $tables;
        }

        $sets = $oracle['sets'] ?? [];
        $aggs = $oracle['aggs'] ?? [];
        $personsByContact = $state['personsByContact'] ?? [];
        $dealProducts = $state['dealProducts'] ?? [];
        $dealProductSnapshots = $state['dealProductSnapshots'] ?? [];

        $ownerIds = [];
        foreach ($deals as $deal) {
            $ownerId = trim((string)($deal['owner_didar_id'] ?? ''));
            $ownerIds[$ownerId] = true;
        }
        $ownerNames = $this->uiOracleOwnerNames(array_keys($ownerIds));
        $productNames = $this->uiOracleProductNames();

        $openCountsByContact = [];
        foreach ($deals as $dealId => $deal) {
            $status = (string)($deal['status'] ?? '');
            if ($status === 'Won' || $status === 'Lost') {
                continue;
            }
            if (!$this->uiOracleDealPassNoDate($deal, $state, $filters)) {
                continue;
            }
            $contactId = trim((string)($deal['contact_didar_id'] ?? ''));
            if ($contactId === '') {
                continue;
            }
            $openCountsByContact[$contactId] = ($openCountsByContact[$contactId] ?? 0) + 1;
        }

        $rowsByKey = [];
        $summaryByUser = [];
        $productStatus = [];
        $productSold = [];
        $productReq = [];
        $productFailed = [];

        $ensureRow = function (string $date, string $ownerId, string $ownerName) use (&$rowsByKey): array {
            $dayKey = $date . '|' . $ownerId;
            if (!isset($rowsByKey[$dayKey])) {
                $rowsByKey[$dayKey] = [
                    'date' => $date,
                    'owner_didar_id' => $ownerId,
                    'owner_name' => $ownerName,
                    'deal_total' => 0,
                    'deal_new_total' => 0,
                    'deal_old_total' => 0,
                    'deal_pending_total' => 0,
                    'deal_pending_new' => 0,
                    'deal_pending_old' => 0,
                    'deal_with_sales_total' => 0,
                    'deal_with_sales_new' => 0,
                    'deal_with_sales_old' => 0,
                    'deal_without_sales_total' => 0,
                    'deal_without_sales_new' => 0,
                    'deal_without_sales_old' => 0,
                    'deal_won_total' => 0,
                    'deal_won_new' => 0,
                    'deal_won_old' => 0,
                    'deal_lost_total' => 0,
                    'deal_lost_new' => 0,
                    'deal_lost_old' => 0,

                    'outstanding_deals_count' => 0,
                    'settled_deals_count' => 0,
                    'outstanding_amount_sum' => 0.0,
                    'settled_contract_amount_sum' => 0.0,
                    'payments_sum' => 0.0,

                    'conversion_rate_total_num' => 0,
                    'conversion_total_den' => 0,
                    'conversion_rate_new_num' => 0,
                    'conversion_new_den' => 0,
                    'conversion_rate_old_num' => 0,
                    'conversion_old_den' => 0,
                    'sales_total_count' => 0,
                    'sales_new_count' => 0,
                    'sales_old_count' => 0,
                    'total_deals_count' => 0,
                    'total_new_deals_count' => 0,
                    'total_old_deals_count' => 0,
                    'total_collected_amount' => 0.0,

                    'sum_conversion_days_total' => 0,
                    'count_conversion_days_total' => 0,
                    'sum_conversion_days_new' => 0,
                    'count_conversion_days_new' => 0,
                    'sum_conversion_days_old' => 0,
                    'count_conversion_days_old' => 0,
                    'sum_failure_days_total' => 0,
                    'count_failure_days_total' => 0,
                    'sum_failure_days_new' => 0,
                    'count_failure_days_new' => 0,
                    'sum_failure_days_old' => 0,
                    'count_failure_days_old' => 0,
                    'payments_after_deal_count' => 0,
                    'payments_before_deal_count' => 0,
                    'payments_same_day_count' => 0,
                ];
            }
            return $rowsByKey[$dayKey];
        };

        foreach ($deals as $dealId => $deal) {
            $dealId = (string)$dealId;

            $dealDate = (string)($deal['deal_date'] ?? '');
            $ownerId = trim((string)($deal['owner_didar_id'] ?? ''));
            $ownerName = (string)($ownerNames[$ownerId] ?? 'بدون مسئول');
            $contactId = trim((string)($deal['contact_didar_id'] ?? ''));
            $contactLeadDate = (string)($personsByContact[$contactId] ?? '');
            $leadType = ($contactLeadDate !== '' && $contactLeadDate === $dealDate) ? 'new' : 'old';
            $status = (string)($deal['status'] ?? '');
            $statusGroup = $status === 'Won' ? 'won' : ($status === 'Lost' ? 'lost' : 'pending');
            $payable = (float)($deal['payable_amount'] ?? 0);
            $inConversionCohort = isset($sets['conversion_cohort'][$dealId]);
            $salesHasPayment = isset($sets['sales_card_qualified'][$dealId]) ? 1 : 0;
            $conversionHasPayment = isset($sets['conversion_converted'][$dealId]) ? 1 : 0;
            $salesTotalPaid = (float)($aggs['sales_all'][$dealId]['total'] ?? 0);
            $salesFirstDate = (string)($aggs['sales_first_all'][$dealId]['first'] ?? '');
            $paymentAllPaid = (float)($aggs['payment_all'][$dealId]['total'] ?? 0);
            $statusGroup = $status === 'Lost' ? 'lost' : (($salesHasPayment || $paymentAllPaid > 0) ? 'won' : 'pending');
            $isSettled = isset($sets['financial_settled'][$dealId]);
            $isOutstanding = isset($sets['financial_outstanding'][$dealId]);
            $hasOpenDeal = ($contactId !== '' && (($openCountsByContact[$contactId] ?? 0) > 0)) ? 1 : 0;
            $lostDate = substr((string)($deal['lost_time'] ?? ''), 0, 10);
            $failureReason = $this->uiOracleFailureReasonFromDeal($deal);

            // deal_status / conversion_status / conversion_time are cohort-valid and rowed by deal_date
            if ($inConversionCohort) {
                $r =& $rowsByKey[$dealDate . '|' . $ownerId];
                if (!isset($r)) {
                    $tmp = $ensureRow($dealDate, $ownerId, $ownerName);
                    $r =& $rowsByKey[$dealDate . '|' . $ownerId];
                }

                $r['deal_total']++;
                $r['total_deals_count']++;
                $r['conversion_total_den']++;
                if ($leadType === 'new') {
                    $r['deal_new_total']++;
                    $r['total_new_deals_count']++;
                    $r['conversion_new_den']++;
                } else {
                    $r['deal_old_total']++;
                    $r['total_old_deals_count']++;
                    $r['conversion_old_den']++;
                }

                if ($statusGroup === 'pending') {
                    $r['deal_pending_total']++;
                    if ($leadType === 'new') { $r['deal_pending_new']++; } else { $r['deal_pending_old']++; }
                } elseif ($statusGroup === 'won') {
                    $r['deal_won_total']++;
                    if ($leadType === 'new') { $r['deal_won_new']++; } else { $r['deal_won_old']++; }
                } elseif ($statusGroup === 'lost') {
                    $r['deal_lost_total']++;
                    if ($leadType === 'new') { $r['deal_lost_new']++; } else { $r['deal_lost_old']++; }
                }

                // Use sales-card-qualified for display consistency, but keep conversion numerators cohort-valid only.
                if ($salesHasPayment) {
                    $r['deal_with_sales_total']++;
                    $r['sales_total_count']++;
                    $r['total_collected_amount'] += $salesTotalPaid;
                    if ($leadType === 'new') {
                        $r['deal_with_sales_new']++;
                        $r['sales_new_count']++;
                    } else {
                        $r['deal_with_sales_old']++;
                        $r['sales_old_count']++;
                    }
                } else {
                    if (isset($sets['partition_without_payment_valid'][$dealId]) && $statusGroup === 'pending') {
                        $r['deal_without_sales_total']++;
                        if ($leadType === 'new') { $r['deal_without_sales_new']++; } else { $r['deal_without_sales_old']++; }
                    }
                }
                if ($conversionHasPayment) {
                    $r['conversion_rate_total_num']++;
                    if ($leadType === 'new') { $r['conversion_rate_new_num']++; } else { $r['conversion_rate_old_num']++; }
                }

                // conversion_time aligned with summary (exclude negative conversion days)
                if ($conversionHasPayment && $salesFirstDate !== '' && $dealDate !== '') {
                    $dd = (int)((new \DateTime($salesFirstDate))->diff(new \DateTime($dealDate)))->format('%r%a');
                    if ($dd >= 0) {
                        if ($dd > 0) { $r['payments_after_deal_count']++; } else { $r['payments_same_day_count']++; }
                        $r['sum_conversion_days_total'] += $dd;
                        $r['count_conversion_days_total']++;
                        if ($leadType === 'new') {
                            $r['sum_conversion_days_new'] += $dd;
                            $r['count_conversion_days_new']++;
                        } else {
                            $r['sum_conversion_days_old'] += $dd;
                            $r['count_conversion_days_old']++;
                        }
                    } else {
                        $r['payments_before_deal_count']++;
                    }
                }
                if ($statusGroup === 'lost' && $lostDate !== '' && $dealDate !== '') {
                    $fd = (int)((new \DateTime($lostDate))->diff(new \DateTime($dealDate)))->format('%r%a');
                    $r['sum_failure_days_total'] += $fd;
                    $r['count_failure_days_total']++;
                    if ($leadType === 'new') {
                        $r['sum_failure_days_new'] += $fd;
                        $r['count_failure_days_new']++;
                    } else {
                        $r['sum_failure_days_old'] += $fd;
                        $r['count_failure_days_old']++;
                    }
                }
                unset($r);
            }

            // payment_status / summary_by_user / product_* use sales-card-qualified cohort and row by first sales date
            if ($salesHasPayment && $salesFirstDate !== '') {
                $rPay =& $rowsByKey[$salesFirstDate . '|' . $ownerId];
                if (!isset($rPay)) {
                    $tmp = $ensureRow($salesFirstDate, $ownerId, $ownerName);
                    $rPay =& $rowsByKey[$salesFirstDate . '|' . $ownerId];
                }
                $rPay['payments_sum'] += $paymentAllPaid;
                if ($isSettled) {
                    $rPay['settled_deals_count']++;
                    $rPay['settled_contract_amount_sum'] += $payable;
                } elseif ($isOutstanding) {
                    $rPay['outstanding_deals_count']++;
                    $rPay['outstanding_amount_sum'] += max(0.0, $payable - $paymentAllPaid);
                }
                unset($rPay);
            }

            // summary_by_user
            if (!isset($summaryByUser[$ownerId])) {
                $summaryByUser[$ownerId] = [
                    'owner_didar_id' => $ownerId,
                    'owner_name' => $ownerName,
                    'total_deals_count' => 0,
                    'sales_total_count' => 0,
                    'total_new_deals_count' => 0,
                    'sales_new_count' => 0,
                    'total_collected_amount' => 0.0,
                    'outstanding_amount_sum' => 0.0,
                ];
            }
            $su =& $summaryByUser[$ownerId];
            if ($inConversionCohort) {
                $su['total_deals_count']++;
                if ($leadType === 'new') { $su['total_new_deals_count']++; }
            }
            if ($salesHasPayment) {
                $su['sales_total_count']++;
                $su['total_collected_amount'] += $salesTotalPaid;
                if ($leadType === 'new') { $su['sales_new_count']++; }
            }
            if ($salesHasPayment && $isOutstanding) {
                $su['outstanding_amount_sum'] += max(0.0, $payable - $paymentAllPaid);
            }

            // product tables (requested by deal_date cohort-valid, sold by sales-card-qualified)
            $primaryProductId = $this->uiOraclePrimaryProductIdForDeal($deal, $dealProducts);
            $productName = $this->uiOracleResolvedProductName($deal, $primaryProductId, $productNames, $dealProductSnapshots);
            if ($productName !== '') {
                if ($inConversionCohort) {
                    $pkReq = $dealDate . '|' . $ownerId . '|' . $productName;
                    if (!isset($productReq[$pkReq])) {
                        $productReq[$pkReq] = ['date'=>$dealDate,'owner_didar_id'=>$ownerId,'owner_name'=>$ownerName,'product_name'=>$productName,'requested_new_count'=>0,'requested_old_count'=>0,'requested_total_count'=>0,'sold_total_count'=>0,'unsold_total_count'=>0];
                    }
                    $productReq[$pkReq]['requested_total_count']++;
                    if ($leadType === 'new') { $productReq[$pkReq]['requested_new_count']++; } else { $productReq[$pkReq]['requested_old_count']++; }
                    if (!$salesHasPayment) {
                        $productReq[$pkReq]['unsold_total_count']++;
                        if ($statusGroup === 'lost') {
                            $productFailed[$pkReq] = $productFailed[$pkReq] ?? ['date'=>$dealDate,'owner_didar_id'=>$ownerId,'owner_name'=>$ownerName,'product_name'=>$productName,'failed_new_count'=>0,'failed_old_count'=>0,'failed_total_count'=>0];
                            $productFailed[$pkReq]['failed_total_count']++;
                            if ($leadType === 'new') { $productFailed[$pkReq]['failed_new_count']++; } else { $productFailed[$pkReq]['failed_old_count']++; }
                        }
                    }
                }
                if ($salesHasPayment) {
                    $salesDate = $salesFirstDate !== '' ? $salesFirstDate : $dealDate;
                    $pkSales = $salesDate . '|' . $ownerId . '|' . $productName;
                    if (!isset($productStatus[$pkSales])) {
                        $productStatus[$pkSales] = [
                            'date' => $salesDate,
                            'owner_didar_id' => $ownerId,
                            'owner_name' => $ownerName,
                            'product_name' => $productName,
                            'sales_total_count' => 0,
                            'sales_new_count' => 0,
                            'sales_old_count' => 0,
                        ];
                    }
                    if (!isset($productSold[$pkSales])) {
                        $productSold[$pkSales] = ['date'=>$salesDate,'owner_didar_id'=>$ownerId,'owner_name'=>$ownerName,'product_name'=>$productName,'sold_new_count'=>0,'sold_old_count'=>0,'sold_total_count'=>0];
                    }
                    $productStatus[$pkSales]['sales_total_count']++;
                    $productSold[$pkSales]['sold_total_count']++;
                    if ($leadType === 'new') {
                        $productStatus[$pkSales]['sales_new_count']++;
                        $productSold[$pkSales]['sold_new_count']++;
                    } else {
                        $productStatus[$pkSales]['sales_old_count']++;
                        $productSold[$pkSales]['sold_old_count']++;
                    }
                    if ($inConversionCohort) {
                        $pkReqSales = $dealDate . '|' . $ownerId . '|' . $productName;
                        if (!isset($productReq[$pkReqSales])) {
                            $productReq[$pkReqSales] = ['date'=>$dealDate,'owner_didar_id'=>$ownerId,'owner_name'=>$ownerName,'product_name'=>$productName,'requested_new_count'=>0,'requested_old_count'=>0,'requested_total_count'=>0,'sold_total_count'=>0,'unsold_total_count'=>0];
                        }
                        $productReq[$pkReqSales]['sold_total_count']++;
                    }
                }
            }

            // payment_mix (distinct leads by deal_date/owner buckets)
            // We'll compute later from deal rows to avoid duplicate contact handling here.
            unset($r, $su);
        }

        // build payment_mix + failure_reasons from cohort-valid deals
        $paymentMixContacts = [];
        $failureRows = [];
        foreach ($deals as $dealId => $deal) {
            $dealId = (string)$dealId;
            if (!isset($sets['conversion_cohort'][$dealId])) {
                continue;
            }
            $dealDate = (string)($deal['deal_date'] ?? '');
            $ownerId = trim((string)($deal['owner_didar_id'] ?? ''));
            $ownerName = (string)($ownerNames[$ownerId] ?? 'بدون مسئول');
            $contactId = trim((string)($deal['contact_didar_id'] ?? ''));
            if ($contactId === '') {
                continue;
            }
            $contactLeadDate = (string)($personsByContact[$contactId] ?? '');
            $leadType = ($contactLeadDate !== '' && $contactLeadDate === $dealDate) ? 'new' : 'old';
            $status = (string)($deal['status'] ?? '');
            $statusGroup = $status === 'Won' ? 'won' : ($status === 'Lost' ? 'lost' : 'pending');
            $lostDate = substr((string)($deal['lost_time'] ?? ''), 0, 10);
            $isSalesQualified = isset($sets['sales_card_qualified'][$dealId]) ? 1 : 0;
            $mixDate = $isSalesQualified ? ((string)($aggs['sales_first_all'][$dealId]['first'] ?? '') ?: $dealDate) : $dealDate;
            $mixKey = $mixDate . '|' . $ownerId;
            if (!isset($paymentMixContacts[$mixKey])) {
                $paymentMixContacts[$mixKey] = [
                    'date' => $mixDate,
                    'owner_didar_id' => $ownerId,
                    'owner_name' => $ownerName,
                    'new_leads_with_payment' => [],
                    'new_leads_without_payment' => [],
                    'new_leads_failed' => [],
                    'old_leads_with_payment' => [],
                    'old_leads_without_payment' => [],
                    'old_leads_failed' => [],
                ];
            }
            $bucket =& $paymentMixContacts[$mixKey];
            // Count by deal (not distinct contact) to match KPI cards/cardinality across surfaces
            $bucketKey = $dealId;
            if ($leadType === 'new') {
                if (isset($sets['partition_lost_valid'][$dealId])) $bucket['new_leads_failed'][$bucketKey] = true;
                elseif (isset($sets['sales_card_qualified'][$dealId])) $bucket['new_leads_with_payment'][$bucketKey] = true;
                elseif (isset($sets['partition_without_payment_valid'][$dealId])) $bucket['new_leads_without_payment'][$bucketKey] = true;
            } else {
                if (isset($sets['partition_lost_valid'][$dealId])) $bucket['old_leads_failed'][$bucketKey] = true;
                elseif (isset($sets['sales_card_qualified'][$dealId])) $bucket['old_leads_with_payment'][$bucketKey] = true;
                elseif (isset($sets['partition_without_payment_valid'][$dealId])) $bucket['old_leads_without_payment'][$bucketKey] = true;
            }
            unset($bucket);

            if ($statusGroup === 'lost') {
                $failureReason = $this->uiOracleFailureReasonFromDeal($deal);
                $hasOpenDeal = ($contactId !== '' && (($openCountsByContact[$contactId] ?? 0) > 0)) ? 1 : 0;
                $failureDate = $lostDate !== '' ? $lostDate : $dealDate;
                $fk = $failureDate . '|' . $ownerId . '|' . $failureReason;
                if (!isset($failureRows[$fk])) {
                    $failureRows[$fk] = [
                        'date' => $failureDate,
                        'owner_didar_id' => $ownerId,
                        'owner_name' => $ownerName,
                        'failure_reason' => $failureReason,
                        'failed_without_current_count' => 0,
                        'failed_new_count' => 0,
                        'failed_old_count' => 0,
                    ];
                }
                if ($hasOpenDeal === 0) $failureRows[$fk]['failed_without_current_count']++;
                if ($leadType === 'new') $failureRows[$fk]['failed_new_count']++; else $failureRows[$fk]['failed_old_count']++;
            }
        }

        $tableDealStatus = [];
        $tablePaymentStatus = [];
        $tableConversionStatus = [];
        $tableConversionTime = [];
        $tableSummaryByUser = [];

        foreach ($rowsByKey as $row) {
            $tableDealStatus[] = [
                'date' => $row['date'],
                'owner_didar_id' => $row['owner_didar_id'],
                'owner_name' => $row['owner_name'],
                'deal_total' => (int)$row['deal_total'],
                'deal_new_total' => (int)$row['deal_new_total'],
                'deal_old_total' => (int)$row['deal_old_total'],
                'deal_pending_total' => (int)$row['deal_pending_total'],
                'deal_pending_new' => (int)$row['deal_pending_new'],
                'deal_pending_old' => (int)$row['deal_pending_old'],
                'deal_with_sales_total' => (int)$row['deal_with_sales_total'],
                'deal_with_sales_new' => (int)$row['deal_with_sales_new'],
                'deal_with_sales_old' => (int)$row['deal_with_sales_old'],
                'deal_without_sales_total' => (int)$row['deal_without_sales_total'],
                'deal_without_sales_new' => (int)$row['deal_without_sales_new'],
                'deal_without_sales_old' => (int)$row['deal_without_sales_old'],
                'deal_won_total' => (int)$row['deal_won_total'],
                'deal_won_new' => (int)$row['deal_won_new'],
                'deal_won_old' => (int)$row['deal_won_old'],
                'deal_lost_total' => (int)$row['deal_lost_total'],
                'deal_lost_new' => (int)$row['deal_lost_new'],
                'deal_lost_old' => (int)$row['deal_lost_old'],
            ];
            $tablePaymentStatus[] = [
                'date' => $row['date'],
                'owner_didar_id' => $row['owner_didar_id'],
                'owner_name' => $row['owner_name'],
                'outstanding_deals_count' => (int)$row['outstanding_deals_count'],
                'settled_deals_count' => (int)$row['settled_deals_count'],
                'outstanding_amount_sum' => (float)$row['outstanding_amount_sum'],
                'settled_contract_amount_sum' => (float)$row['settled_contract_amount_sum'],
                'payments_sum' => (float)$row['payments_sum'],
            ];

            $total = (int)$row['conversion_total_den'];
            $newTotal = (int)$row['conversion_new_den'];
            $oldTotal = (int)$row['conversion_old_den'];
            $salesTotal = (int)$row['sales_total_count'];
            $salesNew = (int)$row['sales_new_count'];
            $salesOld = (int)$row['sales_old_count'];
            $tableConversionStatus[] = [
                'date' => $row['date'],
                'owner_didar_id' => $row['owner_didar_id'],
                'owner_name' => $row['owner_name'],
                'conversion_rate_total' => $total > 0 ? round(($salesTotal / $total) * 100, 2) : 0.0,
                'non_conversion_rate_total' => $total > 0 ? round((($total - $salesTotal) / $total) * 100, 2) : 0.0,
                'conversion_rate_new' => $newTotal > 0 ? round(($salesNew / $newTotal) * 100, 2) : 0.0,
                'non_conversion_rate_new' => $newTotal > 0 ? round((($newTotal - $salesNew) / $newTotal) * 100, 2) : 0.0,
                'conversion_rate_old' => $oldTotal > 0 ? round(($salesOld / $oldTotal) * 100, 2) : 0.0,
                'non_conversion_rate_old' => $oldTotal > 0 ? round((($oldTotal - $salesOld) / $oldTotal) * 100, 2) : 0.0,
                'sales_total_count' => $salesTotal,
                'sales_new_count' => $salesNew,
                'sales_old_count' => $salesOld,
                'total_deals_count' => $total,
                'total_new_deals_count' => $newTotal,
                'total_old_deals_count' => $oldTotal,
                'total_collected_amount' => (float)$row['total_collected_amount'],
                'outstanding_amount_sum' => (float)$row['outstanding_amount_sum'],
            ];

            $ct = (int)$row['count_conversion_days_total'];
            $cn = (int)$row['count_conversion_days_new'];
            $co = (int)$row['count_conversion_days_old'];
            $ft = (int)$row['count_failure_days_total'];
            $fn = (int)$row['count_failure_days_new'];
            $fo = (int)$row['count_failure_days_old'];
            $tableConversionTime[] = [
                'date' => $row['date'],
                'owner_didar_id' => $row['owner_didar_id'],
                'owner_name' => $row['owner_name'],
                'avg_conversion_days_total' => $ct > 0 ? round(((float)$row['sum_conversion_days_total']) / $ct, 2) : 0.0,
                'sum_conversion_days_total' => (float)$row['sum_conversion_days_total'],
                'count_conversion_days_total' => $ct,
                'avg_conversion_days_new' => $cn > 0 ? round(((float)$row['sum_conversion_days_new']) / $cn, 2) : 0.0,
                'sum_conversion_days_new' => (float)$row['sum_conversion_days_new'],
                'count_conversion_days_new' => $cn,
                'avg_conversion_days_old' => $co > 0 ? round(((float)$row['sum_conversion_days_old']) / $co, 2) : 0.0,
                'sum_conversion_days_old' => (float)$row['sum_conversion_days_old'],
                'count_conversion_days_old' => $co,
                'avg_failure_days_total' => $ft > 0 ? round(((float)$row['sum_failure_days_total']) / $ft, 2) : 0.0,
                'sum_failure_days_total' => (float)$row['sum_failure_days_total'],
                'count_failure_days_total' => $ft,
                'avg_failure_days_new' => $fn > 0 ? round(((float)$row['sum_failure_days_new']) / $fn, 2) : 0.0,
                'sum_failure_days_new' => (float)$row['sum_failure_days_new'],
                'count_failure_days_new' => $fn,
                'avg_failure_days_old' => $fo > 0 ? round(((float)$row['sum_failure_days_old']) / $fo, 2) : 0.0,
                'sum_failure_days_old' => (float)$row['sum_failure_days_old'],
                'count_failure_days_old' => $fo,
                'payments_after_deal_count' => (int)$row['payments_after_deal_count'],
                'payments_before_deal_count' => (int)$row['payments_before_deal_count'],
                'payments_same_day_count' => (int)$row['payments_same_day_count'],
            ];
        }

        foreach ($summaryByUser as &$su) {
            $su['conversion_rate_total'] = (int)$su['total_deals_count'] > 0 ? round(($su['sales_total_count'] / $su['total_deals_count']) * 100, 2) : 0.0;
            $su['conversion_rate_new'] = (int)$su['total_new_deals_count'] > 0 ? round(($su['sales_new_count'] / $su['total_new_deals_count']) * 100, 2) : 0.0;
            foreach (['total_collected_amount','outstanding_amount_sum'] as $fld) {
                $su[$fld] = (float)$su[$fld];
            }
        }
        unset($su);
        $tableSummaryByUser = array_values($summaryByUser);

        $tablePaymentMix = [];
        foreach ($paymentMixContacts as $row) {
            $tablePaymentMix[] = [
                'date' => $row['date'],
                'owner_didar_id' => $row['owner_didar_id'],
                'owner_name' => $row['owner_name'],
                'new_leads_with_payment' => count($row['new_leads_with_payment']),
                'new_leads_without_payment' => count($row['new_leads_without_payment']),
                'new_leads_failed' => count($row['new_leads_failed']),
                'old_leads_with_payment' => count($row['old_leads_with_payment']),
                'old_leads_without_payment' => count($row['old_leads_without_payment']),
                'old_leads_failed' => count($row['old_leads_failed']),
            ];
        }

        $overrides = [
            'deal_status' => $this->uiOracleSortRows($tableDealStatus, ['date','owner_name']),
            'payment_mix' => $this->uiOracleSortRows($tablePaymentMix, ['date','owner_name']),
            'payment_status' => $this->uiOracleSortRows($tablePaymentStatus, ['date','owner_name']),
            'conversion_status' => $this->uiOracleSortRows($tableConversionStatus, ['date','owner_name']),
            'conversion_time' => $this->uiOracleSortRows($tableConversionTime, ['date','owner_name']),
            'failure_reasons' => $this->uiOracleSortRows(array_values($failureRows), ['date','owner_name','failure_reason']),
            'summary_by_user' => $this->uiOracleSortRows($tableSummaryByUser, ['owner_name']),
            'product_status' => $this->uiOracleSortRows(array_values($productStatus), ['date','owner_name','product_name']),
            'product_sold_by_owner_day' => $this->uiOracleSortRows(array_values($productSold), ['date','owner_name','product_name']),
            'product_requests_by_owner_day' => $this->uiOracleSortRows(array_values($productReq), ['date','owner_name','product_name']),
            'product_failed_by_owner_day' => $this->uiOracleSortRows(array_values($productFailed), ['date','owner_name','product_name']),
        ];

        foreach ($overrides as $key => $rows) {
            $tables[$key] = $rows;
        }
        return $tables;
    }

    private function uiOracleOwnerNames(array $ownerIds): array
    {
        $ownerIds = array_values(array_filter(array_map('strval', $ownerIds), static fn($v) => $v !== ''));
        if (empty($ownerIds)) {
            return [];
        }
        $placeholders = implode(',', array_fill(0, count($ownerIds), '?'));
        $rows = $this->runSelectAll("SELECT didar_user_id, display_name FROM users WHERE didar_user_id IN ({$placeholders})", $ownerIds);
        $out = [];
        foreach ($rows as $row) {
            $out[(string)$row['didar_user_id']] = (string)($row['display_name'] ?? 'بدون مسئول');
        }
        return $out;
    }

    private function uiOracleProductNames(): array
    {
        $rows = $this->runSelectAll("SELECT id, name FROM products");
        $out = [];
        foreach ($rows as $row) {
            $out[(string)$row['id']] = (string)($row['name'] ?? '');
        }
        return $out;
    }

    private function uiOraclePrimaryProductIdForDeal(array $deal, array $dealProducts): ?string
    {
        $dealId = (string)($deal['id'] ?? '');
        if ($dealId === '' || empty($dealProducts[$dealId])) {
            return null;
        }
        $ids = array_keys($dealProducts[$dealId]);
        sort($ids, SORT_NATURAL);
        return (string)($ids[0] ?? '');
    }

    private function uiOracleResolvedProductName(array $deal, ?string $productId, array $productNames, array $dealProductSnapshots = []): string
    {
        $hint = '';
        $title = trim((string)($deal['title'] ?? ''));
        if ($title !== '' && strpos($title, '|') !== false) {
            $parts = array_map('trim', explode('|', $title));
            $hint = (string)end($parts);
        }
        if ($hint === '') {
            $requested = trim((string)($deal['requested_services'] ?? ''));
            if ($requested !== '') {
                $pieces = preg_split('/[,،]/u', $requested);
                $hint = trim((string)($pieces[0] ?? ''));
            }
        }
        $snapshot = '';
        $dealId = (string)($deal['id'] ?? '');
        if ($dealId !== '' && $productId !== null) {
            $snapshotRow = $dealProductSnapshots[$dealId][(string)$productId] ?? [];
            $snapshot = trim((string)(
                ($snapshotRow['service_title_snapshot'] ?? '')
                ?: ($snapshotRow['product_name_snapshot'] ?? '')
            ));
        }
        $name = $hint !== '' ? $hint : ($snapshot !== '' ? $snapshot : (string)($productNames[(string)$productId] ?? ''));
        return $this->normalizeProductName($name);
    }

    private function normalizeProductName(string $name): string
    {
        $name = trim($name);
        if ($name === '') {
            return '';
        }
        $lower = function_exists('mb_strtolower') ? mb_strtolower($name, 'UTF-8') : strtolower($name);
        $containsVipChannel = function_exists('mb_strpos')
            ? (mb_strpos($lower, 'کانال vip') !== false)
            : (stripos($lower, 'vip') !== false && (strpos($name, 'کانال') !== false));
        if ($lower === 'vip' || $containsVipChannel) {
            return 'کانال VIP';
        }
        return $name;
    }

    private function uiOracleFailureReasonFromDeal(array $deal): string
    {
        $candidates = [
            trim((string)($deal['failure_reason_text'] ?? '')),
            trim((string)($deal['failure_reason_description'] ?? '')),
        ];
        foreach ($candidates as $candidate) {
            if ($candidate !== '') {
                return $candidate;
            }
        }
        $code = trim((string)($deal['failure_reason_code'] ?? ''));
        $map = [
            'no_financial_ability' => 'عدم توان مالی',
            'no_purchase_intent' => 'فعلا قصد خرید ندارد',
            'competitor_purchase' => 'خرید از رقبا',
            'wrong_number' => 'شماره اشتباه',
            'irrelevant_lead' => 'لید نامرتبط',
            'other' => 'سایر',
        ];
        if ($code !== '' && isset($map[$code])) {
            return $map[$code];
        }
        return $code !== '' ? $code : 'نامشخص';
    }

    private function buildLostReasonBreakdownFromOracle(array $oracle): array
    {
        $state = (array)($oracle['state'] ?? []);
        $deals = (array)($state['deals'] ?? []);
        $lostSet = (array)($oracle['sets']['partition_lost_valid'] ?? []);
        $buckets = [];

        foreach (array_keys($lostSet) as $dealId) {
            $deal = $deals[(string)$dealId] ?? null;
            if (!is_array($deal)) {
                continue;
            }
            $reason = $this->uiOracleFailureReasonFromDeal($deal);
            if ($reason === '') {
                $reason = 'نامشخص';
            }
            if (!isset($buckets[$reason])) {
                $buckets[$reason] = [
                    'reason' => $reason,
                    'count' => 0,
                    'total_amount' => 0.0,
                ];
            }
            $buckets[$reason]['count']++;
            $buckets[$reason]['total_amount'] += (float)($deal['payable_amount'] ?? 0);
        }

        $rows = array_values($buckets);
        usort($rows, static function ($a, $b) {
            return ((int)($b['count'] ?? 0)) <=> ((int)($a['count'] ?? 0));
        });
        return $rows;
    }

    private function uiOracleSortRows(array $rows, array $keys): array
    {
        usort($rows, function (array $a, array $b) use ($keys) {
            foreach ($keys as $key) {
                $av = (string)($a[$key] ?? '');
                $bv = (string)($b[$key] ?? '');
                $cmp = strcmp($av, $bv);
                if ($cmp !== 0) {
                    return $cmp;
                }
            }
            return 0;
        });
        return $rows;
    }

    private function runSelectAll(string $sql, array $params = []): array
    {
        $stmt = $this->db->getPdo()->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    private function normalizeCsvList($raw): string
    {
        if (is_array($raw)) {
            $items = $raw;
        } else {
            $raw = trim((string)$raw);
            if ($raw === '') {
                return '';
            }
            $items = explode(',', $raw);
        }

        $normalized = [];
        foreach ($items as $item) {
            $value = strtolower(trim((string)$item));
            if ($value === '' || $value === 'all') {
                continue;
            }
            $normalized[$value] = true;
        }

        return implode(',', array_keys($normalized));
    }

    private function csvHasValue(string $csv, string $needle): bool
    {
        if ($csv === '') {
            return false;
        }
        $values = explode(',', $csv);
        return in_array(strtolower($needle), $values, true);
    }

    private function csvHasAll(string $csv, array $needles): bool
    {
        if ($csv === '') {
            return false;
        }
        $values = explode(',', $csv);
        foreach ($needles as $needle) {
            if (!in_array(strtolower((string)$needle), $values, true)) {
                return false;
            }
        }
        return true;
    }
}
